Skip to content

Commit 29aca3b

Browse files
authored
Merge pull request #87462 from callstack-internal/perf-send-message-phase-2-3
refactor: PureReportActionItem, add ApprovalFlowContent
2 parents 4a24621 + 048fd46 commit 29aca3b

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, isPolicyMember, isPolicyOwner} from '@libs/PolicyUtils';
79+
import {getCleanedTagName, isPolicyAdmin, isPolicyMember, isPolicyOwner} from '@libs/PolicyUtils';
8080
import {containsActionableFollowUps, parseFollowupsFromHtml} from '@libs/ReportActionFollowupUtils';
8181
import {
8282
extractLinksFromMessageHtml,
@@ -170,8 +170,6 @@ import {
170170
getWorkspaceTagUpdateMessage,
171171
getWorkspaceTaxUpdateMessage,
172172
getWorkspaceUpdateFieldMessage,
173-
hasPendingDEWApprove,
174-
hasPendingDEWSubmit,
175173
isActionableAddPaymentCard,
176174
isActionableCardFraudAlert,
177175
isActionableJoinRequest,
@@ -188,7 +186,6 @@ import {
188186
isDeletedAction,
189187
isDeletedParentAction as isDeletedParentActionUtils,
190188
isIOURequestReportAction,
191-
isMarkAsClosedAction,
192189
isMessageDeleted,
193190
isMoneyRequestAction,
194191
isPendingRemove,
@@ -250,6 +247,7 @@ import type * as OnyxTypes from '@src/types/onyx';
250247
import type {Errors} from '@src/types/onyx/OnyxCommon';
251248
import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage';
252249
import {isEmptyObject, isEmptyValueObject} from '@src/types/utils/EmptyObject';
250+
import ApprovalFlowContent, {isApprovalFlowAction} from './actionContents/ApprovalFlowContent';
253251
import SimpleMessageContent, {isSimpleMessageAction} from './actionContents/SimpleMessageContent';
254252
import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions';
255253
import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu';
@@ -1364,47 +1362,16 @@ function PureReportActionItem({
13641362
originalReport={originalReport}
13651363
/>
13661364
);
1367-
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) || isMarkAsClosedAction(action)) {
1368-
const wasSubmittedViaHarvesting = !isMarkAsClosedAction(action) ? (getOriginalMessage(action)?.harvesting ?? false) : false;
1369-
const isDEWPolicy = hasDynamicExternalWorkflow(policy);
1370-
1371-
const isPendingAdd = action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
1372-
if (wasSubmittedViaHarvesting) {
1373-
children = (
1374-
<ReportActionItemMessageWithExplain
1375-
message={translate('iou.automaticallySubmitted')}
1376-
action={action}
1377-
childReport={childReport}
1378-
originalReport={originalReport}
1379-
/>
1380-
);
1381-
} else if (hasPendingDEWSubmit(reportMetadata, isDEWPolicy) && isPendingAdd) {
1382-
children = <ReportActionItemBasicMessage message={translate('iou.queuedToSubmitViaDEW')} />;
1383-
} else if (isDEWPolicy) {
1384-
// Don't show a memo for DEW actions, it's shown in the Concierge action below
1385-
children = <ReportActionItemBasicMessage message={translate('iou.submitted')} />;
1386-
} else {
1387-
children = <ReportActionItemBasicMessage message={translate('iou.submitted', getOriginalMessage(action)?.message)} />;
1388-
}
1389-
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.APPROVED)) {
1390-
const wasAutoApproved = getOriginalMessage(action)?.automaticAction ?? false;
1391-
const isDEWPolicy = hasDynamicExternalWorkflow(policy);
1392-
const isPendingAdd = action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
1393-
1394-
if (wasAutoApproved) {
1395-
children = (
1396-
<ReportActionItemMessageWithExplain
1397-
message={translate('iou.automaticallyApproved')}
1398-
action={action}
1399-
childReport={childReport}
1400-
originalReport={originalReport}
1401-
/>
1402-
);
1403-
} else if (hasPendingDEWApprove(reportMetadata, isDEWPolicy) && isPendingAdd) {
1404-
children = <ReportActionItemBasicMessage message={translate('iou.queuedToApproveViaDEW')} />;
1405-
} else {
1406-
children = <ReportActionItemBasicMessage message={translate('iou.approvedMessage')} />;
1407-
}
1365+
} else if (isApprovalFlowAction(action)) {
1366+
children = (
1367+
<ApprovalFlowContent
1368+
action={action}
1369+
policy={policy}
1370+
reportMetadata={reportMetadata}
1371+
childReport={childReport}
1372+
originalReport={originalReport}
1373+
/>
1374+
);
14081375
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) {
14091376
const wasAutoPaid = getOriginalMessage(action)?.automaticAction ?? false;
14101377
const paymentType = getOriginalMessage(action)?.paymentType;
@@ -1453,17 +1420,6 @@ function PureReportActionItem({
14531420
children = <ReportActionItemBasicMessage message={getReimbursedMessage(translate, action, report, currentUserAccountID)} />;
14541421
} else if (isSimpleMessageAction(action)) {
14551422
children = <SimpleMessageContent action={action} />;
1456-
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)) {
1457-
const wasAutoForwarded = getOriginalMessage(action)?.automaticAction ?? false;
1458-
if (wasAutoForwarded) {
1459-
children = (
1460-
<ReportActionItemBasicMessage>
1461-
<RenderHTML html={`<comment><muted-text>${translate('iou.automaticallyForwarded')}</muted-text></comment>`} />
1462-
</ReportActionItemBasicMessage>
1463-
);
1464-
} else {
1465-
children = <ReportActionItemBasicMessage message={translate('iou.forwarded')} />;
1466-
}
14671423
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.CORPORATE_UPGRADE) {
14681424
children = <ReportActionItemBasicMessage message={translate('workspaceActions.upgradedWorkspace')} />;
14691425
} 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)