Skip to content

[Due for payment 2026-08-03] Spends - Sort function on column briefly show an empty report page #96074

Closed

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Version Number: 9.4.34-1
Reproducible in staging?: Yes
Reproducible in production?: No
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #95758
Email or phone of affected tester (no customers): N/A
Issue reported by: Applause Internal Team
Bug source: Pull Request QA execution
Device used: Windows 11 / Chrome
App Component: Search

Action Performed:

Preconditions: Fresh login to any account that has many report expenses

Steps:

  1. Navigate to spends tab
  2. Click on Report
  3. Click the title column header to see the sort function

Expected Result:

The sort function works well without empty reports page briefly shown

Actual Result:

An empty reports page briefly shown when user clicking the title to sort it, even after user tried to clear cache and restart in troubleshoot. It also happens on other columns and on the expenses tab

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

Bug7208125_1784037103262.bandicam_2026-07-14_20-24-10-782.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Svetlana Lazutkina
Bug ID: 7208125

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Jul 14, 2026
  2. applause-bot commented on Jul 14, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/edd3a670bb9430cd311af84fd36fbf8bb24b1537b88dd2e841b9158e8a717cec

  3. melvin-bot commented on Jul 14, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #95758

  4. melvin-bot commented on Jul 14, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  5. github-actions commented on Jul 14, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  6. MelvinBot commented on Jul 14, 2026

    @MelvinBot
    Contributor

    Issue Analysis

    Root Cause: The empty-page flash on sort is a regression from #95023 ("Allow workspace and status filter to be negatable", bernhardoj, merged 2026-07-13) — not from #95758, which Melvin auto-assigned. That PR tightened isSearchDataLoaded to require the loaded snapshot's hash to equal the current query hash. Since sortBy/sortOrder are folded into the query hash, clicking any column header produces a new hash and fires a fresh backend search under a new snapshot_<hash> key. While that snapshot loads, the page keeps showing the previous results as a fallback — but those carry the old hash, so isSearchDataLoaded now returns false, shouldShowEmptyState returns true even though data is present, and EmptySearchView flashes until the new snapshot arrives. This matches the symptom exactly: every sortable column, both Reports and Expenses tabs, staging-only.

    Before #95023, that check compared type + status (neither changes on sort), so the old-hash fallback stayed "loaded" and no empty state appeared — which is why it does not reproduce in production.

    Recommended Fix: Two options for the assignee to choose from:

    • Safe (deploy-blocker): revert Allow workspace and status filter to be negatable #95023.
    • Fix-forward: don't show the empty state while non-empty fallback results are being displayed during a sort/filter transition — e.g. have shouldShowEmptyState not fire when dataLength > 0, or treat the old-hash fallback as loaded while isSorting. This preserves the negatable-filter change while closing the flash.
    Investigation details & evidence chain

    Verified against App HEAD b2d9dadbf40d85694acdf4932f5f422549d2e5e4.

    The regressing diff (from gh pr diff 95023):

     function isSearchDataLoaded(searchResults, queryJSON) {
    -    const {status} = queryJSON ?? {};
    -    const sortedSearchResultStatus = ...status...
    -    const sortedQueryJSONStatus = ...status...
    -    const isDataLoaded = (data != null || errors != null) && search?.type === queryJSON?.type && sortedSearchResultStatus === sortedQueryJSONStatus;
    +    const isDataLoaded = (data != null || errors != null) && search?.type === queryJSON?.type && searchResults.search.hash === queryJSON?.hash;

    End-to-end flow:

    1. Column header click → new sortBy/sortOrder → navigation.setParams({q}) (src/components/Search/index.tsx, onSortPress).
    2. Sort params are part of primaryHash → new hash → new Search.search() → empty new snapshot_<hash>. (SearchQueryUtils.ts:600-607)
    3. SearchPage.tsx feeds lastNonEmptySearchResults (OLD hash) downward so the list still has rows.
    4. isSearchDataLoaded(searchResults, queryJSON) → old hash ≠ new queryJSON.hash → false. (SearchUIUtils.ts:4802-4806)
    5. shouldShowEmptyState(false, dataLength, type) → true despite dataLength > 0 → EmptySearchView flashes. (SearchUIUtils.ts:4798-4800)
    6. New snapshot lands → correct sorted data renders.

    Alternative explanations ruled out:

    • PR Pass unavailableTranslation param - batch 1 #95758 (the auto-assigned PR) only touched the sort comparators getSortedTransactionData/getSortedReportData (passing an unavailableTranslation param to getPolicyName). That path affects the ordering values, not the loading/empty-state gate, and only for the policy-name column — it cannot produce an empty flash across all columns and both tabs. Red herring.
    • Not a client-side sort perf issue — the sort itself is synchronous; the flash is caused by the fresh-snapshot fetch triggered by the hash change combined with the stricter loaded-check.

    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/96074) to create a draft PR (fix-forward option).
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's analysis.

  7. bernhardoj commented on Jul 14, 2026

    @bernhardoj
    Contributor

    The analysis above looks correct, but the root cause looks like an existing one. If we open prod and change the status filter, we can reproduce the same issue.

    that check compared type + status (neither changes on sort)

    The analysis above said that it doesn't happen before because we compare the type and status, and neither changes on sort. So, I tried changing the status filter and can observe the same issue on prod.

    web.mp4

    (try Clear cache & restart if you can't repro)

    I think the expected result should be showing a skeleton loader.

  8. jasperhuangg commented on Jul 14, 2026

    @jasperhuangg
    Contributor

    Demoting since we can reproduce this in prod.

  9. 35 remaining items

  10. melvin-bot commented on Jul 30, 2026

    @melvin-bot

    @bernhardoj Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  11. melvin-bot commented on Aug 2, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  12. melvin-bot commented on Aug 2, 2026

    @melvin-bot

    Payment Summary

    Upwork Job

    BugZero Checklist (@mallenexpensify)

    • I have verified the correct assignees and roles are listed above and updated the necessary manual offers
    • I have verified that there are no duplicate or incorrect contracts on Upwork for this job (https://www.upwork.com/ab/applicants//hired)
    • I have verified the PR was not reverted
    • I have applied any discounts due to bugs/regressions introduced by this PR
    • I have paid out the Upwork contracts or cancelled the ones that are incorrect
    • I have verified the payment summary above is correct
  13. aimane-chnaif commented on Aug 3, 2026

    @aimane-chnaif
    Contributor

    BugZero Checklist:

    • [Contributor] The offending PR has been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.

      Link to comment: Not regression. Pre-existing bug in production

    • [Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.

      Link to discussion:

    • [Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.

    • [BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.

      Link to issue: https://github.com/Expensify/Expensify/issues/668914

    Regression Test Proposal

    Precondition:

    Web only
    Prerequisite: have some expenses

    Test:

    1. Open Spend
    2. Press any table header to sort
    3. Verify no empty view is shown briefly when sorting

    Do we agree 👍 or 👎

    Zapier Logs Run ID: 00040eee-6b91-a6c5-3f08-790c8348d2b4
  14. aimane-chnaif commented on Aug 3, 2026

    @aimane-chnaif
    Contributor

    This ended up not being a regression from #95023. We fixed the root cause existed in production.
    Reasoning: #96074 (comment)

  15. mallenexpensify commented on Aug 5, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor: @bernhardoj due $250 via NewDot
    Contributor+: @aimane-chnaif due $250 via NewDot

    ^ Test case created. Thx

    ⚠️ Looks like this issue was linked to a Deploy Blocker #96946

    @aimane-chnaif please address ALL mentioned of deploy blockers, otherwise I have to go dig around to find out why it shouldn't be taken into consideration which takes time away from paying everyone. Thx

  16. flaviadefaria commented on Aug 7, 2026

    @flaviadefaria
    Contributor

    Approved: @bernhardoj due $250 via NewDot

  17. garrettmknight commented on Sep 14, 2026

    @garrettmknight
    Contributor

    $250 approved for @aimane-chnaif

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

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionBugSomething is broken. Auto assigns a BugZero manager.DailyKSv2Engineering

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions