Skip to content

Convert Search footer totals via GetTransactionsConvertedAmount command#94669

Draft
c3024 wants to merge 30 commits into
Expensify:mainfrom
c3024:chaitanya/651977-get-transactions-converted-amount
Draft

Convert Search footer totals via GetTransactionsConvertedAmount command#94669
c3024 wants to merge 30 commits into
Expensify:mainfrom
c3024:chaitanya/651977-get-transactions-converted-amount

Conversation

@c3024

@c3024 c3024 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Re-implements the Search footer currency picker on a dedicated read command, GetTransactionsConvertedAmount (agreed in Expensify #651977), replacing the auxiliary-snapshot approach from #90777 (reverted in #94562) whose first-page-only coverage forced the picker to disable for out-of-page selections. The command reuses Search's query/aggregation (so numbers match) and converts on demand — whole search, selected transactions (transactionIDList), selected reports (reportIDList), or a grouped view's groups + grand total — merging results into a new searchFooterConversion Onyx cache without touching the live snapshot. The footer reads that cache (fetching only what's missing), converts per row so a grouped selection can mix groups and loose transactions, works with nothing selected, and fetches again when an inline edit moves a stamped source value.

Backend (both merged): Auth #22515, Web #54039.

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/651977
PROPOSAL:

Tests

  1. Open Search and switch to the Expenses view, with expenses spanning more than one currency.
  2. Verify the footer shows the total spend in the default currency.
  3. Open the footer total currency picker and choose a different currency.
  4. Verify the total briefly shows a loading skeleton and then displays the converted total in the selected currency.
  5. Select a subset of expenses and verify the footer shows the selected subset total in the selected currency.
  6. Select expenses that fall beyond the first page of results and verify the total still converts correctly (the picker stays enabled).
  7. Switch to the Reports view, select one or more reports, choose a different currency, and verify the footer total converts (showing the default-currency total until the conversion arrives).
  8. Open a grouped search (e.g. group by category), choose a different currency with nothing selected and verify the grand total converts, then select one or more groups and verify the selected total converts.
  9. In that grouped search, select a whole group and also expand a different group and tick an individual transaction; verify the footer total converts and includes both the group and the individual transaction.
  10. Reset the picker to the default currency and verify the original total returns.
  11. Verify that no errors appear in the JS console.
  • Verify that no errors appear in the JS console

Offline tests

  1. Choose a custom footer currency, then go offline.
  2. Verify the currency picker does not trigger a conversion while offline and the footer keeps showing the last known total.
  3. Go back online and verify converting the footer currency works again.

QA Steps

Same as tests.

  • 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 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 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)
  • 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)
  • 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.
  • 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
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-07-22.at.12.29.12.PM.mov
Screen.Recording.2026-07-22.at.12.20.11.PM.mov
Screen.Recording.2026-07-22.at.12.13.13.PM.mov
Screen.Recording.2026-07-22.at.12.11.55.PM.mov
Screen.Recording.2026-07-22.at.12.10.36.PM.mov
Screen.Recording.2026-07-22.at.12.00.34.PM.mov
Screen.Recording.2026-07-22.at.11.57.59.AM.mov
Screen.Recording.2026-07-21.at.6.18.03.PM.mov
Screen.Recording.2026-07-21.at.6.15.52.PM.mov
Screen.Recording.2026-07-21.at.5.57.00.PM.mov
Screen.Recording.2026-07-21.at.5.51.10.PM.mov
Screen.Recording.2026-07-21.at.5.45.25.PM.mov

c3024 added 3 commits June 26, 2026 12:42
…7-chaitanya/634354-search-footer-target-currency-app"

This reverts commit b3cc25d, reversing
changes made to 38dd266.
Replace the auxiliary-snapshot footer-currency conversion with the dedicated
GetTransactionsConvertedAmount Auth command. The command merges converted
figures into a new searchFooterConversion Onyx cache (by transaction and by
query hash, each nested under the target currency) via onyxData, leaving the
live search snapshot in its original currency.

The footer reads converted totals from the cache and fetches coverage on
demand only when the cache is missing what it needs, so the picker stays
enabled for partial selections instead of being disabled when a selection
falls outside snapshot coverage. Removes the now-obsolete
buildFlatQueryWithoutGroupBy helper and the search() targetCurrency param.
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/ONYXKEYS.ts 100.00% <ø> (ø)
src/components/Button/index.tsx 93.37% <100.00%> (+0.08%) ⬆️
src/components/ButtonComposed/Button.tsx 98.85% <100.00%> (+0.02%) ⬆️
...omponents/Search/FilterComponents/SingleSelect.tsx 79.48% <ø> (ø)
...nents/Search/FilterDropdowns/SingleSelectPopup.tsx 0.00% <ø> (ø)
src/libs/API/types.ts 100.00% <ø> (ø)
src/libs/ExportOnyxState/common.ts 80.35% <ø> (ø)
src/pages/Search/SearchPage.tsx 88.00% <100.00%> (+0.24%) ⬆️
.../Search/FilterComponents/ListFilterViewWrapper.tsx 26.66% <0.00%> (ø)
...components/Search/FilterDropdowns/DisplayPopup.tsx 0.00% <0.00%> (ø)
... and 5 more
... and 35 files with indirect coverage changes

c3024 added 9 commits June 26, 2026 14:18
- Drop unused ConvertedAmountByCurrency/ConvertedTotalByCurrency exports
- Remove the always-true currentSearchHash undefined guard
- Collapse getObjectMember/getNumberMember into one getGroupCount helper
- Drop the live-refresh custom-currency reset (kept the group-selection reset)
Converted footer totals are ephemeral, session-scoped display data, so drop
the SEARCH_FOOTER_CONVERSION cache when the Search screen unmounts rather than
persisting it across sessions. Matches the issue's reset-on-navigate-away
semantics and bounds the cache to a single Search session.
On the Reports search (type:expense-report), convert a selection by report
instead of by transaction: derive the selected report IDs, read/sum their
converted totals from the new SEARCH_FOOTER_CONVERSION `reports` cache section,
and fetch uncovered report IDs via getFooterConvertedAmounts({reportIDList}).
Also stop showing the footer skeleton for a client-side subtotal (partial
selection) while the whole-search total is still loading, and only show the
conversion skeleton when there are IDs to fetch.
Selecting every loaded report made selectedExpenseCount === metadataCount, which
routed the footer to the whole-search query path — invalid on the Reports search,
where the backend returns report rows (no flat per-transaction rows) and errored
with "No column named groupAmount". Now any explicit (loaded) report selection,
including when all loaded reports are selected, converts via the per-report path.
The whole-search query path is skipped on the Reports search, falling back to the
default total instead of erroring or showing a stuck skeleton.
Enable the footer currency picker for grouped searches. A grouped search
converts its selection by group key (new SEARCH_FOOTER_CONVERSION `groups`
section, summed like reports), and the picker stays enabled on grouped views.
The whole-search grand total now converts for aggregate (report/grouped)
searches with nothing selected too, resolving from the cached searchTotals that
every search type now returns — so the picker is no longer inert there.
A grouped search can select whole groups and individual transactions from other
groups at once. Convert per row instead of treating the selection as groups
only: whole-group entries from the `groups` cache, individual transactions from
the `transactions` cache, summed together (reports stay aggregate-by-report).
The effect fetches both the grouped totals and the loose transactions as needed.

Since the whole-search grand total is now converted for every search type,
all-selected falls to that grand total, so the all-selected-aggregate special
case is dropped (shouldUseClientTotal = !metadataCount || hasPartialSelection).
c3024 added 3 commits July 6, 2026 12:55
…amount

# Conflicts:
#	src/components/Button/index.tsx
#	src/components/Search/FilterDropdowns/DisplayPopup.tsx
#	src/components/Search/FilterDropdowns/GroupCurrencyPopup.tsx
#	src/components/Search/SearchPageFooter.tsx
#	src/components/Search/SearchSelectionFooter.tsx
#	src/libs/actions/Search.ts
…transactions-converted-amount

# Conflicts:
#	src/components/ButtonComposed/composed/ButtonWithIcons.tsx
#	src/components/Search/FilterComponents/SingleSelect.tsx
#	src/components/Search/SearchPageFooter.tsx
#	src/components/Search/SearchSelectionFooter.tsx
@c3024 c3024 changed the title [HOLD for Auth/Web] Convert Search footer totals via GetTransactionsConvertedAmount command Convert Search footer totals via GetTransactionsConvertedAmount command Jul 16, 2026
@c3024

c3024 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

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

Copy link
Copy Markdown

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: c852c69a6e

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

Comment thread src/libs/actions/Search.ts Outdated
Comment thread src/libs/actions/Search.ts
…detection

Instead of writing the source stamp optimistically before the read returns
(which briefly, or indefinitely on a dropped/failed read, makes a stale
conversion look fresh), send the stamps to GetTransactionsConvertedAmount so it
echoes them back in the same onyxData merge as the converted values. The stamp
and its value are then written together and a stale entry is never shown as
fresh.
@c3024

c3024 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@c3024 c3024 changed the title Convert Search footer totals via GetTransactionsConvertedAmount command [HOLD] Convert Search footer totals via GetTransactionsConvertedAmount command Jul 16, 2026

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

Copy link
Copy Markdown

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: 3545772dc0

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

Comment thread src/components/Search/SearchSelectionFooter.tsx
Comment thread src/components/Search/SearchSelectionFooter.tsx Outdated
c3024 added 3 commits July 16, 2026 14:11
No conversion can complete offline, so an offline edit or selection change that
leaves a conversion missing/stale should not flip the footer into the loading
skeleton (which would stay until connectivity returns). Guard the converting
state and the fetch effect with isOffline; the effect re-runs on reconnect.
A failed read leaves no converted value, so the freshness check never passes
and the footer would show a loading skeleton indefinitely. Record the failed
target currency (cleared when a fresh request for it is issued) and drop the
converting state, falling back to the default total; the effect still retries
on an edit, currency change, or reconnect.
Drops the backend source-stamp echo (Auth/Web follow-ups descoped): the App
stamps sources optimistically again (in the read's optimisticData) instead of
sending them to the command. Keeps the App-only offline guard and the
read-failure fallback (fall back to the default total instead of a stuck
skeleton or a stale converted value).
@c3024 c3024 changed the title [HOLD] Convert Search footer totals via GetTransactionsConvertedAmount command Convert Search footer totals via GetTransactionsConvertedAmount command Jul 20, 2026
c3024 added 8 commits July 20, 2026 18:15
…transactions-converted-amount

# Conflicts:
#	src/ONYXKEYS.ts
#	src/libs/ExportOnyxState/common.ts
The Onyx errors convention (OnyxCommon Errors) is a displayable message map;
this field is a silent per-currency failure flag, so name it for what it is.
Group rows only render on grouped expense/trip searches, where the groups
conversion path handles them, so a selected group can never persistently meet a
non-grouped footer. The one-frame window during a grouping switch (selection
clears in an effect) is already inert because a hash change resets
selectedCurrency. Drops the guard, the currency reset, and the
shouldAllowCurrencyChange prop whose false branch is now unreachable.
The backend alters what a query matches based on searchKey (e.g. unapprovedCash
excludes card expenses), so a conversion request without it can cover a
different expense set than the snapshot — on suggested tabs like Cash accruals
the footer count changed when switching currency. Serialize searchKey into the
conversion jsonQuery exactly as search() does.
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.

1 participant