Skip to content

fix: migrate ReportActionItem to useOnyx for policyTags#80258

Merged
Beamanator merged 19 commits intomainfrom
marcochavezf/migrate-modified-expense-to-useOnyx
Feb 11, 2026
Merged

fix: migrate ReportActionItem to useOnyx for policyTags#80258
Beamanator merged 19 commits intomainfrom
marcochavezf/migrate-modified-expense-to-useOnyx

Conversation

@marcochavezf
Copy link
Contributor

@marcochavezf marcochavezf commented Jan 22, 2026

Explanation of Change

This PR properly migrates the ReportActionItem and ContextMenuActions components to use the useOnyx hook for policyTags and session data, addressing the code review feedback about deprecated Onyx.connect patterns.

Changes:

  • ReportActionItem.tsx:

    • Import getForReportActionTemp instead of getForReportAction
    • Add useLocalize hook to get translate function
    • Add useOnyx hook to fetch policyTags and session
    • Pass translate, policy, policyTags, and currentUserLogin to getForReportActionTemp
  • ContextMenuActions.tsx:

    • Add currentUserLogin to destructuring in copy to clipboard action
    • Pass currentUserLogin to getForReportActionTemp
  • BaseReportActionContextMenu.tsx:

    • Add useOnyx hook for session
    • Pass currentUserLogin in the payload
  • ModifiedExpenseMessage.ts:

    • Add currentUserLogin parameter to getForReportActionTemp
    • Add eslint-disable comments for remaining Onyx.connectWithoutView calls

Why this approach:
The getForReportActionTemp function accepts parameters instead of using module-level Onyx connections. This allows React components to properly re-render when the underlying data changes and follows the recommended pattern of making functions pure.

Fixed Issues

Part of #80263

Tests

  • Verify that no errors appear in the JS console
  1. Open an expense report with modified expense actions
  2. Verify the modified expense messages display correctly
  3. Right-click on a modified expense action and select "Copy to clipboard"
  4. Verify the copied text matches the displayed message

Offline tests

N/A - This change does not affect offline behavior. The data fetching pattern remains the same.

QA Steps

Same as test steps

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native

N/A - No UI changes, this is a refactor

Android: mWeb Chrome

N/A - No UI changes, this is a refactor

iOS: Native

N/A - No UI changes, this is a refactor

iOS: mWeb Safari

N/A - No UI changes, this is a refactor

MacOS: Chrome / Safari

N/A - No UI changes, this is a refactor

@marcochavezf marcochavezf requested review from a team as code owners January 22, 2026 19:55
@melvin-bot melvin-bot bot requested review from MonilBhavsar and heyjennahay and removed request for a team January 22, 2026 19:55
@melvin-bot
Copy link

melvin-bot bot commented Jan 22, 2026

@MonilBhavsar 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]

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 388ce58c06

ℹ️ 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".

@codecov
Copy link

codecov bot commented Jan 22, 2026

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/libs/ModifiedExpenseMessage.ts 78.24% <100.00%> (+24.82%) ⬆️
src/pages/inbox/report/ReportActionItem.tsx 100.00% <100.00%> (ø)
...es/inbox/report/ContextMenu/ContextMenuActions.tsx 0.66% <0.00%> (-0.01%) ⬇️
... and 387 files with indirect coverage changes

@marcochavezf
Copy link
Contributor Author

@linhvovan29546 do you want to review this one as C+, since you have context about the migration?

@linhvovan29546
Copy link
Contributor

@marcochavezf Thanks for mention! Please assign this to me, since it came from the original PR I reviewed as well.

Copy link
Contributor

@heyjennahay heyjennahay left a comment

Choose a reason for hiding this comment

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

Product review not required

Copy link
Contributor

@linhvovan29546 linhvovan29546 left a comment

Choose a reason for hiding this comment

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

We still need QA, so could you please remove the [No QA] tag from the title?

This properly migrates the ReportActionItem component to use the useOnyx
hook for policyTags data instead of relying on the deprecated module-level
Onyx.connect pattern in ModifiedExpenseMessage.ts.

Changes:
- Import getForReportActionTemp instead of getForReportAction
- Add useLocalize hook to get translate function
- Add useOnyx hook to fetch policyTags for the reports policy
- Pass translate, policy, and policyTags to getForReportActionTemp
- Add eslint-disable comments for remaining Onyx.connectWithoutView calls
  (used by utility files that will be migrated in follow-up PRs)
The function was still using module-level currentUserLogin from Onyx.connect.
Now it accepts currentUserLogin as a parameter for full migration.

Changes:
- Add currentUserLogin parameter to getForReportActionTemp
- Update ReportActionItem.tsx to pass currentUserLogin from session
- Update ContextMenuActions.tsx to pass currentUserLogin
- Update BaseReportActionContextMenu.tsx to fetch session and pass currentUserLogin
@marcochavezf marcochavezf changed the title [No QA] fix: migrate ReportActionItem to useOnyx for policyTags fix: migrate ReportActionItem to useOnyx for policyTags Feb 4, 2026
- Use useCurrentUserPersonalDetails hook for currentUserLogin instead of session
- Use policyForMovingExpensesID when policyID is fake for policy tags
- Update ESLint disable comments with more specific justifications
@marcochavezf marcochavezf force-pushed the marcochavezf/migrate-modified-expense-to-useOnyx branch from 62f9930 to 128592e Compare February 4, 2026 02:23
@marcochavezf
Copy link
Contributor Author

@MelvinBot please fix the eslint and typescript errors

@MelvinBot
Copy link
Contributor

Fixed the failing ESLint and TypeScript checks.

Issue: The code was destructuring currentUserLogin from the payload, but the ContextMenuActionPayload type defines currentUserPersonalDetails (not currentUserLogin). The code then tried to use currentUserPersonalDetails?.login which didn't exist since only currentUserLogin was destructured.

Fix: Changed the destructured variable from currentUserLogin to currentUserPersonalDetails to match the type definition. This resolves all three errors:

  • TS2339: Property 'currentUserLogin' does not exist on type 'ContextMenuActionPayload'
  • TS2552: Cannot find name 'currentUserPersonalDetails'
  • ESLint: 'currentUserLogin' is defined but never used

Beamanator
Beamanator previously approved these changes Feb 10, 2026
Copy link
Contributor

@Beamanator Beamanator left a comment

Choose a reason for hiding this comment

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

Both concerns from my previous review have been addressed in cbec8b1a3fb:

  1. Billable/Reimbursable localization -- getForReportActionTemp now translates raw values (e.g. "billable"translate('common.billable').toLowerCase()) before passing to buildMessageFragmentForValue, matching the original getForReportAction behavior.

  2. AI-generated fallback -- The empty-message fallback with reportActionOriginalMessage?.aiGenerated check has been added, producing "changed the expense based on past activity" when appropriate.

Tests for all three cases (billable, reimbursable, AI fallback) have been added and verify the correct translated output.

The overall migration approach (adding useOnyx for policyTags in ReportActionItem, passing currentUserLogin from useCurrentUserPersonalDetails, and the policyIDForTags fallback for moved expenses) looks solid as PR 1 of the migration plan in #80263.

Minor note for future PRs: test coverage for tag changes, tax amount/rate, date, attendees, and policyRulesModifiedFields would further strengthen confidence in behavioral parity, but is not blocking.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cbec8b1a3f

ℹ️ 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".

Co-authored-by: Marco Chávez <marcochavezf@users.noreply.github.com>
@MelvinBot
Copy link
Contributor

Fixed the Prettier formatting in src/libs/ModifiedExpenseMessage.ts (commit 0515fda). The multi-line buildMessageFragmentForValue calls were collapsed to single lines by Prettier since the arguments are now stored in local variables.

@marcochavezf
Copy link
Contributor Author

Regarding the codex-connector thread about DEFAULT_TAG_LIST fallback in getForReportActionTemp:

This PR already handles the fake policyID case in ReportActionItem.tsx:

const policyIDForTags = report?.policyID === CONST.POLICY.OWNER_EMAIL_FAKE && policyForMovingExpensesID ? policyForMovingExpensesID : report?.policyID;
const [policyTags] = useOnyx(...);

The remaining concern is about the transient state where policyTags is undefined before Onyx loads the data (missing the ?? CONST.POLICY.DEFAULT_TAG_LIST fallback from getForReportAction). However, this is not a new regression -- the old connectWithoutView approach had the same timing issue during initial load. The useOnyx hook will trigger a re-render once the data loads, at which point the tag change message will render correctly. Resolved the thread accordingly.

@marcochavezf
Copy link
Contributor Author

@codex review
@claude review

🤖 This comment was generated with the assistance of an AI tool.

getMessageLine(translate, `\n${translate('iou.removed')}`, removalFragments);

if (message === '') {
// If we don't have enough structured information to build a detailed message but we
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these changes necessary for this PR? If not, can you please remove it so that it is more straight-forward to review?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, these changes are necessary. Beamanator flagged in his review that getForReportActionTemp was missing the aiGenerated fallback that exists in getForReportAction. Since this PR migrates ReportActionItem to call getForReportActionTemp instead of getForReportAction, without this fallback there would be a behavioral regression: AI-attributed expense modifications would display "changed the expense" instead of "changed the expense based on past activity."

The same reasoning applies to the billable/reimbursable localization fix just above — both were required to maintain parity with the function being replaced.

🤖 This comment was generated with the assistance of an AI tool.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0515fdaaf7

ℹ️ 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".

…Tags

Update the comment on policyIDForTags to explain why a fake policyID requires
falling back to policyForMovingExpensesID. Add CONST.POLICY.DEFAULT_TAG_LIST
fallback when policyTags is undefined to prevent tag-only edits from degrading
to the generic "changed the expense" message.

Co-authored-by: Marco Chávez <marcochavezf@users.noreply.github.com>
@marcochavezf
Copy link
Contributor Author

@codex review @claude review

🤖 This comment was generated with the assistance of an AI tool.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d18ddbb6e

ℹ️ 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".

const originalReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${originalReportID}`];
const isOriginalReportArchived = useReportIsArchived(originalReportID);
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const {accountID: currentUserAccountID, login: currentUserLogin} = useCurrentUserPersonalDetails();

Choose a reason for hiding this comment

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

P2 Badge Use session email fallback for currentUserLogin

useCurrentUserPersonalDetails() does not guarantee login is populated (the provider backfills email from session, but not login), so extracting login here and later passing currentUserLogin ?? '' into getForReportActionTemp can make isPolicyAdmin() evaluate false while personal details are still loading, which drops the admin-only workspace rules link from MCC-based modified-expense messages. The previous implementation used session-backed login directly, so this is a behavior regression; please fall back to email when login is absent (and apply the same fallback where this payload is built for context-menu copy).

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch — verified this is a real regression. The CurrentUserPersonalDetailsProvider backfills email from session but not login, so login can be undefined during loading. The old code used session.email which was always available.

@MelvinBot Please fix this by using email instead of login in both locations:

  1. In src/pages/inbox/report/ReportActionItem.tsx:
// Change:
const {accountID: currentUserAccountID, login: currentUserLogin} = useCurrentUserPersonalDetails();
// To:
const {accountID: currentUserAccountID, email: currentUserEmail} = useCurrentUserPersonalDetails();

And update the usage: currentUserLogin: currentUserEmail ?? ''

  1. In src/pages/inbox/report/ContextMenuActions.tsx, change:
currentUserLogin: currentUserPersonalDetails?.login ?? '',
// To:
currentUserLogin: currentUserPersonalDetails?.email ?? '',

This maintains parity with the old session-backed email value.

🤖 This comment was generated with the assistance of an AI tool.

CurrentUserPersonalDetailsProvider backfills email from session but not
login, so login can be undefined during loading. Use email to maintain
parity with the old session-backed value.

Co-authored-by: Marco Chávez <marcochavezf@users.noreply.github.com>
@marcochavezf
Copy link
Contributor Author

@codex review @claude review

🤖 This comment was generated with the assistance of an AI tool.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. 👍

ℹ️ 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".

@marcochavezf
Copy link
Contributor Author

Updated and ready for another review

@marcochavezf marcochavezf requested a review from tgolen February 11, 2026 04:31
Copy link
Contributor

@tgolen tgolen left a comment

Choose a reason for hiding this comment

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

Nice, that comment it better!

Copy link
Contributor

@Beamanator Beamanator left a comment

Choose a reason for hiding this comment

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

Re-reviewed the latest changes (3 new commits since my last approval). Everything looks good:

  1. loginemail fix — Correct choice since useCurrentUserPersonalDetails backfills email from session but not login. Prevents isPolicyAdmin() from getting an empty string during loading, which would drop the admin workspace rules link.

  2. DEFAULT_TAG_LIST fallbackpolicyTags: policyTags ?? CONST.POLICY.DEFAULT_TAG_LIST matches the original getForReportAction behavior and prevents tag-only edits from degrading to the generic "changed the expense" during initial load/cache misses.

  3. Improved comment on policyIDForTags — Now clearly explains why the fake policyID fallback is needed, not just what it does.

Previous fixes (billable/reimbursable localization, AI fallback, test coverage) remain intact. No new concerns found. CI is green.

^ Added by Opus 4.6 - but I also manually reviewed and agree 🙏

@Beamanator Beamanator merged commit 28b988d into main Feb 11, 2026
38 checks passed
@Beamanator Beamanator deleted the marcochavezf/migrate-modified-expense-to-useOnyx branch February 11, 2026 16:29
@github-actions
Copy link
Contributor

🚧 @Beamanator has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify
Copy link
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/Beamanator in version: 9.3.18-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/lakchote in version: 9.3.18-8 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants