Skip to content

Thread beta overrides into the remaining util call sites - #100061

Draft
arekm213 wants to merge 25 commits into
Expensify:mainfrom
arekm213:arekm213/feat/98409-beta-overrides-utils
Draft

Thread beta overrides into the remaining util call sites#100061
arekm213 wants to merge 25 commits into
Expensify:mainfrom
arekm213:arekm213/feat/98409-beta-overrides-utils

Conversation

@arekm213

@arekm213 arekm213 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Follow up to #99444, which added local beta overrides but left three betas resolving from the raw betas array, so an override never reached them. This depends on #99444 and must merge after it, since it uses the betaOverrides parameter added to Permissions.isBetaEnabled there.

The three remaining betas are now override aware:

  • asapSubmit and defaultRooms: the betas parameter is replaced by the resolved boolean (isASAPSubmitBetaEnabled, isDefaultRoomsBetaEnabled), passed down from usePermissions at the hook or component boundary. This is the pattern the codebase already uses in about 400 places for nine other betas: resolve once where usePermissions is available, then pass the answer down. Since usePermissions merges betas, configuration and overrides, nothing downstream can drift.
  • vendorMatching: ViolationsUtils resolves this beta itself, so it now reads BETA_OVERRIDES next to the BETAS subscription it already had. Every caller of getViolationsOnyxData is an optimistic data builder and never runs during render, which is why Onyx.connectWithoutView is the right tool here. The same applies to TransactionInlineEdit.

Because the boolean replaces the array, the betas plumbing that existed only to answer these checks is now dead and has been removed, which is why this deletes more lines than it adds.

Two changes here go beyond "the utils see overrides", both deliberate:

betaConfiguration is now applied where it was skipped. ViolationsUtils and TransactionInlineEdit resolved their beta without the configuration, while every UI check resolves it with the configuration through usePermissions. On an account holding the all beta, if the backend lists that beta under explicitOnly or exclusion, the two disagreed, so the app could write an inactive vendor violation for a feature the user cannot see. That predates this PR, but those lines now take the overrides as well, so leaving them configuration blind would have been a strange place to stop. Only accounts on the all beta are affected.

A pre-existing swapped argument pair is fixed. On main, AttachmentPickerWithMenuItems calls createNewReport(personalDetails, isASAPSubmitBetaEnabled, hasViolations, ...) while the signature is (ownerPersonalDetails, hasViolationsParam, isASAPSubmitBetaEnabled, ...). Both are booleans so TypeScript never caught it, and the ten other call sites are correct. Today this only corrupts the next step, because the optimistic report state and status come from the raw betas array further down. This PR moves that resolution to the boolean, which would have made the swap corrupt the state and status as well, so the arguments are put in the right order here rather than left for a separate fix.

Two call sites keep a hardcoded false on purpose. InSelector and UnreadIndicatorUpdater previously passed betas: undefined and betas: [], which resolved to false for everyone, so the literal keeps their behaviour identical. Passing the real value there would change what the Search filter and the unread indicator show, which is unrelated to overrides and belongs in its own PR.

Fixed Issues

$ #98409
PROPOSAL:

Tests

  1. Sign in and open Beta overrides (Cmd/Ctrl+D, or Settings > Troubleshoot on a non production build).
  2. Toggle defaultRooms on and verify #admins and #announce appear in the LHN without a reload. Toggle it off and verify they disappear. Before this PR the LHN ignored the override entirely.
  3. Toggle asapSubmit on, create an expense, and verify the optimistic report state, status and next step match the pinned value. Repeat with the beta pinned off and verify they change accordingly.
  4. With asapSubmit still pinned, approve an expense from Search and verify the pinned value applies there too. Only the pay path honoured it before.
  5. Press Reset all overrides and verify every beta returns to the value the account has on the backend.
  6. Sign in on an account with no overrides set and verify expense creation, submitting, approving and paying behave exactly as they do on main.

The vendorMatching override needs a QuickBooks Online or Xero workspace with a synced vendor list to demonstrate by hand, so it is covered by unit tests instead: tests/unit/ViolationUtilsTest.ts asserts that pinning the beta on adds the inactive supplier violation when the account does not have the beta, and that pinning it off suppresses the violation when the account does have it.

  • Verify that no errors appear in the JS console

Offline tests

Same as the tests above. Overrides are stored locally and only affect frontend checks, so behaviour is identical offline.

QA Steps

Overrides are hidden in production, so QA is a regression pass rather than a feature test: create, submit, approve and pay expenses, open reports from the LHN, and use the in: filter in Search. Everything should behave exactly as it does today.

  • 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 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 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 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

No UI changes in this PR, the beta overrides screen itself is in #99444.

…-beta-overrides

# Conflicts:
#	src/languages/de.ts
#	src/languages/es.ts
#	src/languages/fr.ts
#	src/languages/it.ts
#	src/languages/ja.ts
#	src/languages/nl.ts
#	src/languages/pl.ts
#	src/languages/pt-BR.ts
#	src/languages/zh-hans.ts
@melvin-bot

melvin-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx bun ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx bun ./scripts/generateTranslations.ts --compare-ref main

@arekm213

arekm213 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@arekm213

arekm213 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@MelvinBot 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: 75beffc515

ℹ️ 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 on lines +27 to +31
function BetaOverridesPage() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isBetaEnabled} = usePermissions();
const [betaOverrides] = useOnyx(ONYXKEYS.BETA_OVERRIDES);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Block the beta override route in production

When an authenticated production user opens /beta-overrides directly, the linking config routes here even though only the Test Tool Menu entry is guarded by !isProduction. This page has no environment check, so it exposes all test switches; because usePermissions ignores overrides in production, toggling a switch writes a hidden override but leaves the effective switch state unchanged. Gate the page or route itself so the production-only restriction cannot be bypassed by a deep link.

Useful? React with 👍 / 👎.

@MelvinBot

Copy link
Copy Markdown
Contributor

Review: 2 blocking test failures, both caused by this PR

The refactor itself is sound — I verified the createNewReport argument-swap fix against the signature and all 11 call sites, and confirmed overrides can't leak into production because Permissions.ts:31 gates on the environment before reading betaOverrides. Two CI failures need fixing first.

🔴 1. Onyx export keys are out of alphabetical order

src/libs/ExportOnyxState/common.ts:172-174 lists BETA_OVERRIDES before BETA_CONFIGURATION. That fails ExportOnyxStateTest › safeOnyxKeys should list its keys alphabetically. Swap the two lines.

🔴 2. useSearchSelector now needs contexts the test mocks away

useSearchSelector/base.ts:199 swapped useOnyx(ONYXKEYS.BETAS) for usePermissions(), which reads BetasContext / BetaConfigurationContext / BetaOverridesContext. But useSearchSelectorTest.tsx:53 replaces the whole @components/OnyxListItemProvider module with just usePersonalDetails, so those contexts are undefined and all 20 tests in the file crash with TypeError: Cannot read properties of undefined (reading '$$typeof').

Fix by spreading jest.requireActual('@components/OnyxListItemProvider') into the mock, or by mocking @hooks/usePermissions directly.

🟡 3. Two test files lost ASAP-submit coverage in the collapse

Where a test passed both isASAPSubmitBetaEnabled: false and betas: [CONST.BETAS.ALL], those were not the same value — the array resolved to true inside shouldCreateNewMoneyRequestReport and getExpenseReportStateAndStatus. Collapsing them to a single false flips which branch the test exercises, silently and without any assertion change:

  • RequestMoneyTest.ts:225 and 12 other requestMoney calls — every one now runs with the beta off, so the optimistic-report state/status path for ASAP submit is no longer covered anywhere in that file.
  • PerDiemTest.ts:337 vs :355 now pass true to getPerDiemExpensePolicyID and false to getPerDiemExpenseInformation in the same test. That is exactly the drift the doc comment on getPerDiemExpensePolicyID tells you to avoid, and it makes earlyPolicyID resolve against a different branch than the information builder.

Please pick the value each test meant, keep the two per-diem calls in sync, and add at least one isASAPSubmitBetaEnabled: true case back.

🟢 4. Nit: dead parameter

betas is still declared on GetApproveActionCallbackParams and still passed by handleActionButtonPress, but getApproveActionCallback no longer reads it. Drop it.

What I checked and found fine
  • isBetaEnabled in dependency arrays. usePermissions memoizes on [betas, betaConfiguration, betaOverrides, isProduction], so swapping betasisBetaEnabled in the useMemo/useCallback deps of SearchAutocompleteList, useSidebarOrderedReports, AmountSubmitDataSync, WorkflowsApprovalsTab and useSearchBulkActions keeps the same invalidation behavior. No extra renders.
  • The argument-swap fix. createNewReport is (ownerPersonalDetails, hasViolationsParam, isASAPSubmitBetaEnabled, policy, betas, …). AttachmentPickerWithMenuItems was the only wrong call site; the other ten already matched. Both of its calls are correct now.
  • The hardcoded false at two call sites. InSelector passed betas: undefined and UnreadIndicatorUpdater passed betas: []; both resolved to false through canSeeDefaultRoom, so the literal is behavior-identical. Agreed these belong in their own PR.
  • The two deliberate behavior changes (applying betaConfiguration in ViolationsUtils and TransactionInlineEdit) only affect accounts on the all beta, and are clearly called out in the description. Worth a second opinion from someone who knows whether vendorMatching is listed under explicitOnly/exclusion, since that decides whether anything actually changes for real users.
  • Production safety. Overrides are read before the betas array in isBetaEnabled, but only when !isProductionEnvironment, so the new Onyx.connectWithoutView reads of BETA_OVERRIDES in ViolationsUtils and TransactionInlineEdit cannot affect production.
  • The third red CI job is not yours. CloudflareSessionTest › propagates an exchange failure fails with ReferenceError: Request is not defined from the expo fetch polyfill — unrelated to these changes.

One process note: this stacks on #99444, which is still open, so it can't merge until that one does.


view run

@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

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/ONYXKEYS.ts 100.00% <ø> (ø)
src/ROUTES.ts 27.31% <ø> (+0.24%) ⬆️
src/SCREENS.ts 100.00% <ø> (ø)
...eaderActions/MoneyReportHeaderSecondaryActions.tsx 0.66% <ø> (ø)
...yReportHeaderPrimaryAction/SubmitPrimaryAction.tsx 1.25% <ø> (+0.01%) ⬆️
...neyReportHeaderPrimaryAction/useConfirmApproval.ts 0.00% <ø> (ø)
...nents/Navigation/QuickCreationActionsBar/index.tsx 92.04% <100.00%> (ø)
src/components/OnyxListItemProvider.tsx 100.00% <100.00%> (ø)
...Item/MoneyRequestReportPreview/PayActionButton.tsx 96.36% <100.00%> (+0.06%) ⬆️
...m/MoneyRequestReportPreview/SubmitActionButton.tsx 97.43% <ø> (-0.07%) ⬇️
... and 67 more
... and 196 files with indirect coverage changes

@arekm213

arekm213 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@arekm213

arekm213 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@MelvinBot review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 7981e80083

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

@MelvinBot

Copy link
Copy Markdown
Contributor

Re-review of 7981e80: the two blockers are fixed, the new test is wrong

Both failures I flagged are gone — ExportOnyxStateTest passes with the key order swapped, and all 20 useSearchSelectorTest tests pass now that the mock spreads jest.requireActual. The dead betas param is gone too, and the coverage restoration in RequestMoneyTest/PerDiemTest matches what main actually exercised. One new test you added is asserting the wrong value.

🔴 The new asapSubmit state and status test fails on a randomized fixture

ReportUtilsTest.ts:19785 expects STATE_NUM.SUBMITTED (1) but gets OPEN (0), so test (job 2) is still red.

The fixture at ReportUtilsTest.ts:19757 pins autoReporting and autoReportingFrequency, but inherits approvalMode from createRandomPolicy, which picks it at random from Object.values(CONST.POLICY.APPROVAL_MODE). For seed 201 it lands on OPTIONAL, which makes isSubmitAndClose true. buildOptimisticEmptyReport passes isEmptyOptimisticReport: true, so the guard at ReportUtils.ts:7334isInstantSubmitEnabledLocal && !(isSubmitAndCloseLocal && isEmptyOptimisticReport) — is false, and the function correctly returns OPEN.

The production code is right; the fixture just doesn't pin the field the assertion depends on. Add approvalMode: CONST.POLICY.APPROVAL_MODE.BASIC to instantSubmitPolicy and it will reach the SUBMITTED branch.

🟢 Nit: leaked mock state

BetaOverridesPageTest.tsx:116 sets mockIsProduction = true and never resets it. It works today only because that it is last in the file. An afterEach(() => { mockIsProduction = false; }) makes it order-independent.

Verified fixed, plus notes on the production gate
  • ExportOnyxState/common.tsBETA_CONFIGURATION now precedes BETA_OVERRIDES; test (job 5) is green.
  • useSearchSelectorTest.tsx — spreading jest.requireActual<typeof OnyxListItemProvider> restores the three beta contexts, so usePermissions() resolves and all 20 tests pass.
  • Dead parambetas removed from both GetApproveActionCallbackParams and the handleActionButtonPress call site.
  • PerDiemTest.tsgetPerDiemExpensePolicyID and getPerDiemExpenseInformation now both receive true, so the two stay in sync as the doc comment requires.
  • RequestMoneyTest.ts — 32 call sites flipped to true, which restores the branch betas: [CONST.BETAS.ALL] used to select on main. The buildOptimisticEmptyReport beta-enabled case is now covered directly too.
  • The new production gate on BetaOverridesPage is reasonable defence-in-depth, though not strictly required: the authoritative guard is already synchronous in Permissions.ts:31, seeded from CONFIG.ENVIRONMENT before the async isProduction() resolves. Note useEnvironment reads from EnvironmentContextProvider, which populates asynchronously, so on a production build the switches could render for a frame before FullPageNotFoundView takes over. Harmless — the toggles can't affect any beta check — but worth knowing if you'd rather gate the route registration instead.
  • CloudflareSessionTest › propagates an exchange failure is still red in test (job 3) with the same ReferenceError: Request is not defined from the expo fetch polyfill. Nothing in this PR touches that path.

view run

@arekm213

arekm213 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@MelvinBot review

@MelvinBot

Copy link
Copy Markdown
Contributor

Re-review of 0aed5c7: everything I flagged is fixed — LGTM

Both points from the last round are addressed and CI agrees: test (job 2) and test (job 5) are now green, along with typecheck, spellcheck, React Compiler Compliance and every other jest shard.

  • The randomized fixture is pinned. ReportUtilsTest.ts:19763 now sets approvalMode: BASIC, so isSubmitAndClose is deterministically false and the beta-disabled case reaches the SUBMITTED branch at ReportUtils.ts:7334 regardless of the seed. The explanatory comment is a nice touch — that trap will catch someone else otherwise.
  • The mock no longer leaks. BetaOverridesPageTest.tsx:70-72 resets mockIsProduction in afterEach, so the production-gate test no longer depends on being last in the file.

No new findings. The only red jest job left is CloudflareSessionTest › propagates an exchange failure, failing with ReferenceError: Request is not defined out of the expo fetch polyfill — that file is not in this PR's diff and nothing here touches that path.

Still blocked on #99444 landing first, since this uses the betaOverrides parameter it adds.


view run

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.

2 participants