fix: Stale "Review fraudulent charge" task appears on home screen but the fraud flag was already cleared#87717
Conversation
…e sensitive section
…revious possible fraud on API failure on fraud action resolution
…SensitiveCards hook tests so fraud-card expectations align with new unresolved-action guard
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
@Krishna2323 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a290fedc9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| if (isCardWithPotentialFraud(card) && card.nameValuePairs?.possibleFraud?.fraudAlertReportID) { | ||
| const fraudAlertReportID = card.nameValuePairs?.possibleFraud?.fraudAlertReportID; | ||
| const hasUnresolvedFraudAction = !!fraudAlertReportID && !!getUnresolvedCardFraudAlertAction(String(fraudAlertReportID)); |
There was a problem hiding this comment.
Subscribe to fraud actions before filtering fraud cards
This filter now depends on getUnresolvedCardFraudAlertAction(...), but the hook only subscribes to ONYXKEYS.CARD_LIST; it does not subscribe to REPORT_ACTIONS. If Home renders before the relevant report actions are loaded, hasUnresolvedFraudAction is false and the fraud task is dropped, and later report-action updates will not trigger a recompute here, so a real unresolved fraud alert can remain hidden until an unrelated card-list change happens.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Done!
I found that it is a real issue, that the useTimeSensitiveCards.ts hook isn't subscribed to report actions but we added the check with report actions.
There was a problem hiding this comment.
@Uzaifm127 I think there's a misunderstanding here. Instead of creating a new selector function and all the extra boilerplate, you could simply subscribe to REPORT_ACTIONS via useOnyx and pass them to getUnresolvedCardFraudAlertAction.
Specifically:
- Add a second optional param (
reportActions) togetUnresolvedCardFraudAlertAction. - Use the passed
reportActionsif available, or fall back to the module-level value. - In
useTimeSensitiveCards, subscribe to the relevant report actions viauseOnyxand pass them in.
This keeps the logic centralized in one function and stays consistent with how the LHN already handles it.
There was a problem hiding this comment.
Krishna, you are correct. Let me clear this further.
If you meant this:
function getUnresolvedCardFraudAlertAction(reportID: string, reportActions?: ReportActions) {
const actions = reportActions ?? getAllReportActions(reportID);
return Object.values(actions).find(
(action) => isActionableCardFraudAlert(action) && !getOriginalMessage(action)?.resolution,
);
}
function useTimeSensitiveCards() {
const [cards] = useOnyx(ONYXKEYS.CARD_LIST);
const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS);
// In the loop - pass subscribed data to the existing function
const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fraudAlertReportID}`];
const hasUnresolvedFraudAction = !!getUnresolvedCardFraudAlertAction(
String(fraudAlertReportID),
reportActions,
);
}Then, I also thought to subscribe all report actions in the hook but this might decrease performance, because if we subscribe to all report actions via useOnyx as above then the hook will re-render on every report action modification (entire Home time-sensitive section re-renders) but if we go with the current approach then we have a selector in useOnyx which will ensures we only re-render when the fraud resolution actually changes.
Both approaches subscribe to all report actions but the difference is the selector function.
either we can choose DRY or Performance here.
You can correct me if I'm wrong here.
There was a problem hiding this comment.
@Uzaifm127 I don’t think this should have any negative impact since it follows the same approach used elsewhere.
@MelvinBot Could you confirm the best way to handle this?
There was a problem hiding this comment.
Krishna's suggestion is the right approach here. Subscribing to REPORT_ACTIONS via useOnyx and passing the data to getUnresolvedCardFraudAlertAction is simpler and consistent with established patterns.
The codebase convention (e.g., OptionRowLHNData.tsx, TransactionItemRowRBR.tsx) is to subscribe to report actions via useOnyx, then pass the raw data to existing helper functions — keeping logic centralized in one place rather than duplicating it with inline selectors.
Uzaifm127's performance concern about re-renders is valid in theory, but with the React Compiler auto-memoizing useTimeSensitiveCards, the derived values will only recompute when their inputs actually change. The selector adds complexity without a meaningful performance benefit in this context.
Recommended approach:
- Add an optional
reportActionsparam togetUnresolvedCardFraudAlertAction(fall back to the module-level value when not provided) - In
useTimeSensitiveCards, subscribe to the relevant report actions per fraud card'sfraudAlertReportIDviauseOnyxand pass them through
This keeps the logic DRY, follows existing patterns, and the React Compiler handles the memoization concern.
There was a problem hiding this comment.
Nice then, I'll refine based on this on tomorrow and I'll look into the failing test.
Resolved the merge conflicts.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 790f48f80f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return result; | ||
| }; | ||
|
|
||
| const [hasUnresolvedFraudByReport] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS, {selector: fraudActionsSelector}, [fraudActionsSelector]); |
There was a problem hiding this comment.
Memoize fraud selector dependency to avoid rerender churn
fraudActionsSelector is recreated on every render and then passed as the dependency to useOnyx(...). Since useOnyx invalidates when dependencies change, this subscription will be invalidated on every render; and because the selector builds a fresh result object each time, the hook can keep producing new snapshots and repeatedly re-render the Home section even when the underlying fraud data has not changed. Memoize the selector (e.g., with useCallback) and key dependencies off stable inputs (like report IDs) rather than the selector function reference.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Krishna, I don't believe we should use useCallback and useMemo as new React compiler will handle it. WDYT?
Fixed the conflicts.
There was a problem hiding this comment.
Hmm correct, useTimeSensitiveCards is auto-memoized.
joekaufmanexpensify
left a comment
There was a problem hiding this comment.
Good for product
…bleFraud through FraudAlertContent and preserving resolveFraudAlert rollback support
|
@Uzaifm127 please resolve conflicts. Reviewing now. |
|
@Krishna2323 Fixed the conflicts. |
|
@Valforte If possible, can we have QA test this PR? I verified it using Onyx data, but it would be great if QA can also test it with a real Expensify Card, with a potentially fraud alert action. |
|
I'll ask for QA review before approving |
|
🚧 @Valforte has triggered a test Expensify/App build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
@Valforte any updates on this? |
|
No update on QA yet, we have a pretty challenging deploy this week that is taking all of QA priority. I'll request QA again but we might still have to wait a while |
|
@Uzaifm127 @Krishna2323 can any of you clarify how to achieve the procondition |
|
Me and Krishna tested this by mocking the Onyx data locally. For the step:
this is not a step which can be triggered by UI easily. It would either need to be triggered by backend on a test account, or by a real Expensify Card transaction being flagged by the fraud/risk system as suspicious. Since that is difficult to reproduce reliably on demand, we validated the flow through local Onyx simulation instead. |
|
Can you provide steps so QA can reproduce with Onyx simulations? |
|
I'll provide the steps on tomorrow Monday. |
|
@Valforte QA can reproduce this with local Onyx simulation using the steps below:
const reportID = Number((await window.report)?.reportID);
const fakeActionID = '9990011';
await window.Onyx.merge(`reportActions_${reportID}`, {
[fakeActionID]: {
originalMessage: {
resolution: 'recognized',
},
},
});
const reportID = Number((await window.report)?.reportID);
const fakeActionID = '9990011';
await window.Onyx.merge(`reportActions_${reportID}`, {
[fakeActionID]: {
originalMessage: {
resolution: 'fraud',
},
},
});
This is the code to cleanup the Onyx mocking data which we mocked: const reportID = Number((await window.report)?.reportID);
const fakeCardID = 999001;
const fakeActionID = '9990011';
await window.Onyx.merge('cardList', {
[fakeCardID]: null,
});
await window.Onyx.merge(`reportActions_${reportID}`, {
[fakeActionID]: null,
});cc: @Krishna2323 |
|
Asking QA for test |
|
Pass, Windows 10/Chrome v9.3.62-3 PR:87717 bandicam.2026-05-06.16-10-43-419.mp4 |
|
@Valforte Please let me know when you are about to merge this PR. I'll merge main before. |
|
I'm ready to merge, please go ahead and merge main |
|
Done. Merged the main. |
|
🚧 @Valforte has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
Hi @Uzaifm127. This PR was already tested here. |
|
🚀 Deployed to staging by https://github.com/Valforte in version: 9.3.69-0 🚀
Bundle Size Analysis (Sentry): |
|
No help site changes are required for this PR. This PR is a bug fix to internal fraud alert filtering logic — it ensures the "Review fraudulent charge" time-sensitive task disappears after the fraud alert is resolved. The existing help site documentation in Expensify-Home-Overview.md already describes the Time-sensitive section correctly (it appears "only when there is something that requires immediate attention" for "potential risk, such as suspected Expensify Card fraud"). No feature names, UI labels, or workflows changed — just the underlying logic for when the alert is shown. |
|
🚀 Deployed to production by https://github.com/Beamanator in version: 9.3.69-18 🚀
|
Explanation of Change
This PR updated the fraud card filtering logic in
useTimeSensitiveCardsto only show the "Review fraudulent charge" time-sensitive task when the associated card has an unresolved fraud alert action in the linked workspace chat.Previously, the Home tab fraud task was displayed solely based on card fraud metadata (
card.fraud/possibleFraud) without verifying whether the fraud alert had already been resolved. Because of this, the fraud review task could remain visible even after the fraud flag was cleared and the card was reactivated.This change fixes the issue by validating that the linked fraud alert report still contains an unresolved fraud action before adding the card to the Home screen’s time-sensitive fraud list, ensuring stale fraud review tasks are no longer shown after resolution.
Fixed Issues
$ #85981
PROPOSAL: #85981 (comment)
Tests
Precondition: You must trigger potential fraudulent transaction flag using Expensify card.
Offline tests
Same as Test
QA Steps
Same as Test
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
android-native.mov
Android: mWeb Chrome
android-mWeb.mov
iOS: Native
iOS-native.mov
iOS: mWeb Safari
iOS-mWeb.mov
MacOS: Chrome / Safari
macOS.mov