Skip to content

[$175] Workflows - Missing hint and role not disabled for Authorized Payer #102392

Open

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: v9.4.94-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: https://test-management.browserstack.com/projects/2219752/test-runs/75704622/folder/53678307/48358512/1798307301?sort=%2Bidentifier
Email or phone of affected tester (no customers): applausetester+vq+2709@applause.expensifail.com
Issue reported by: Applause Internal Team
Bug source: Regression TC Execution
Device used: Windows 10/Chrome
App Component: Workspace Settings

Action Performed:

Preconditions Preconditions
The user is in a workspace with a connected bank account
The workspace has fewer than 8 members with admin role

  1. Navigate to the workspace from preconditions > Workflows
  2. Click on the Payer field to open the Authorized Payer RHP
  3. Set any of the admin as the Payer (Don't select owner) - Click Share
  4. Go to the Members tab
  5. Click on the Admin payer member to view the member detail page
  6. Verify that the role field is disabled and a hint text below it saying: "Role can't be changed because this member is a [payer] on this workspace.

Expected Result:

Verify that the role field is disabled and a hint text below it saying: "Role can't be changed because this member is a [payer] on this workspace.

Actual Result:

Missing hint and role not disabled for Authorized Payer

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7270566_1790493096450.missing_hint_role.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7270566

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @jasperhuangg

Activity

  1. applause-bot commented on Sep 28, 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/9fd6c007d5ed6c118d414f783d87cdf2d4b14395c8f6d91e204a6b6911d4d878

  2. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 28, 2026
  3. MelvinBot commented on Sep 28, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    This is mostly intended behavior, and the test case is out of date. Allow promoting the Authorized Payer to Workspace Admin (merged 2026-08-27) deliberately removed the blanket lock and the "Role can't be changed…" hint. That PR fixed Authorized Payer cannot be promoted to Workspace Admin because the Role field is locked. A later revert, Revert #98123 to resolve Authorized Payer deploy blockers, was closed without merging.

    The payer's Role row now stays editable when there is another payer role they can switch to. The role picker then offers only payer roles. As a result, common.roleCannotBeChanged is no longer used anywhere.

    There is one real gap. PAYER_ROLES includes Editor, but the role picker never offers Editor. On a Collect workspace, an admin payer's Role row looks editable because of Editor. The picker then shows only "Workspace admin", the role they already have. So the row is a dead end, and the lock hint never shows.

    Code references

    What changes do you think we should make in order to solve the problem?

    1. Update the regression test case. On a Control workspace, an admin payer can switch between Workspace Admin and Payments Admin, so the Role row should stay enabled with no hint.
    2. In WorkspaceMemberDetailsPage, count only the payer roles the picker actually offers when you compute canReimburserChangeRole. In practice, leave out Editor.
    3. When the payer has no other role to switch to, disable the row and show common.roleCannotBeChanged with a link to the Workflows payments page. Bring back the useEnvironment URL and shouldRenderHintAsHTML that Allow promoting the Authorized Payer to Workspace Admin #98123 removed.
    4. Change the Collect test above so it expects a disabled row with the hint.

    What alternative solutions did you explore? (Optional)

    If product wants the old blanket lock back, restore !isReimburser in canEditSelectedMemberRole and the hint. That reverts #98123, which the team already declined.

    Open question: I couldn't confirm which roles the backend accepts for the payer, especially Editor. That decides whether Editor should also be removed from PAYER_ROLES.


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


    view run

  4. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Sep 28, 2026
  5. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @getusha (External)

  6. changed the title [-]Workflows - Missing hint and role not disabled for Authorized Payer[/-] [+][$175] Workflows - Missing hint and role not disabled for Authorized Payer[/+] on Sep 28, 2026
  7. melvin-bot commented on Sep 28, 2026

    @melvin-bot
  8. naufalfx805-source commented on Sep 28, 2026

    @naufalfx805-source
  9. naufalfx805-source commented on Sep 28, 2026

    @naufalfx805-source
  10. melvin-bot commented on Sep 28, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. bonnhatnguyen commented on Sep 29, 2026

    @bonnhatnguyen

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    In Workspace Settings > Members, when an Admin is designated as the Authorized Payer on a Collect workspace (or any workspace where no other payer role is assignable), the Role field remains interactive instead of disabled, and the expected hint text "Role can't be changed because this member is a [payer] on this workspace" is completely missing. Tapping on the row navigates to a dead-end role selection page that only offers the role they already hold (Workspace Admin).


    What is the root cause of that problem?

    There are two root causes introduced in PR #98123 (Allow promoting the Authorized Payer to Workspace Admin):

    1. Inclusion of Non-UI Role CONST.POLICY.ROLE.EDITOR in PAYER_ROLES:
      In src/libs/PolicyUtils.ts (lines 704–711):

      function canRolePay(role: string | undefined): boolean {
          return !!role && ROLE_PERMISSION_BUNDLES[role]?.[CONST.POLICY.POLICY_FEATURE.WORKFLOWS_PAYMENTS] === CONST.POLICY.POLICY_FEATURE_ACCESS.WRITE;
      }
      
      const PAYER_ROLES = Object.values(CONST.POLICY.ROLE).filter(canRolePay);

      Because EDITOR_POLICY_FEATURES has WRITE access to WORKFLOWS_PAYMENTS, canRolePay(CONST.POLICY.ROLE.EDITOR) evaluates to true, including editor in PAYER_ROLES.
      However, editor is an internal permission bundle that is never offered in WorkspaceMemberRoleList.tsx.

      In src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx (lines 160–162):

      const assignablePayerRoles = PAYER_ROLES.filter((payerRole) => canMemberAssignRole(policy, currentUserLogin, payerRole));
      const canReimburserChangeRole = assignablePayerRoles.some((payerRole) => payerRole !== member?.role);
      const canEditSelectedMemberRole = !isSelectedMemberOwner && !isSelectedMemberCurrentUser && canManageSelectedMemberRole && (!isReimburser || canReimburserChangeRole);

      For an Admin on a Collect policy, canMemberAssignRole(policy, currentUserLogin, CONST.POLICY.ROLE.EDITOR) returns true (it is not in CONTROL_POLICY_ONLY_ROLES). Thus, assignablePayerRoles contains ['admin', 'editor'].
      canReimburserChangeRole evaluates assignablePayerRoles.some((payerRole) => payerRole !== member?.role), which evaluates to true because 'editor' !== 'admin'!
      This causes canEditSelectedMemberRole to evaluate to true on Collect workspaces even though no other payer roles are actually available.

    2. Omission of hintText and shouldRenderHintAsHTML from MenuItemWithTopDescription:
      In PR Allow promoting the Authorized Payer to Workspace Admin #98123, the hint rendering on the role menu item was removed. Even when canEditSelectedMemberRole is false, the explanatory hint text common.roleCannotBeChanged ("Role can't be changed because this member is a payer on this workspace.") is never rendered.


    What changes do you think we should make in order to solve the problem?

    1. In src/libs/PolicyUtils.ts:
      Filter out CONST.POLICY.ROLE.EDITOR from PAYER_ROLES so that only user-facing selectable roles are considered:

      --- a/src/libs/PolicyUtils.ts
      +++ b/src/libs/PolicyUtils.ts
      @@ -707,7 +707,9 @@ function canRolePay(role: string | undefined): boolean {
           return !!role && ROLE_PERMISSION_BUNDLES[role]?.[CONST.POLICY.POLICY_FEATURE.WORKFLOWS_PAYMENTS] === CONST.POLICY.POLICY_FEATURE_ACCESS.WRITE;
       }
       
      -const PAYER_ROLES = Object.values(CONST.POLICY.ROLE).filter(canRolePay);
      +const PAYER_ROLES = Object.values(CONST.POLICY.ROLE)
      +    .filter((role) => role !== CONST.POLICY.ROLE.EDITOR)
      +    .filter(canRolePay);
    2. In src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx:

      • Re-introduce useEnvironment:
        const {environmentURL} = useEnvironment();
        const workspaceWorkflowsPageURL = `${environmentURL}/${ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID, CONST.TAB.WORKFLOWS.PAYMENTS)}`;
      • Restore the explanatory hint text when the member is an Authorized Payer who cannot change roles:
        <MenuItemWithTopDescription
            disabled={!canEditSelectedMemberRole}
            title={translate(`workspace.common.roleName`, member?.role)}
            interactive={canEditSelectedMemberRole}
            description={translate('common.role')}
            shouldShowRightIcon={canEditSelectedMemberRole}
            shouldGreyOutWhenDisabled={false}
            shouldUseDefaultCursorWhenDisabled
            hintText={isReimburser && !canReimburserChangeRole ? translate('common.roleCannotBeChanged', workspaceWorkflowsPageURL) : undefined}
            shouldRenderHintAsHTML
            pressableTestID="member-role-menu-item"
            onPress={() => {
                ...
            }}
        />

    Automated Unit Tests:

    In tests/ui/WorkspaceMemberDetailsPageTest.tsx:

    • Test 1: On a Collect workspace where an Admin is the Authorized Payer, assert that the role menu item is disabled and renders the common.roleCannotBeChanged hint text containing the workflow payments URL.
    • Test 2: On a Control workspace where an Admin is the Authorized Payer, assert that the role menu item remains interactive and does not render the disabled hint text (allowing transition to Payments Admin).

    What alternative solutions did you explore? (Optional)

    Contributor details
    Your Expensify account email: bonnhatnguyen0@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0139ed4098bdc50182

  12. melvin-bot commented on Sep 29, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  13. 21 remaining items

  14. MelvinBot commented on Nov 3, 2026

    @MelvinBot
    Contributor

    Proposal (updated against current main)

    What is the root cause of that problem?

    My earlier root cause still holds on current main. The code moved, and the same bug now also affects a second screen.

    Allow promoting the Authorized Payer to Workspace Admin removed the blanket lock and the "Role can't be changed…" hint on purpose. The payer's Role row now stays editable when another payer role is available to them.

    That check uses PAYER_ROLES. This list includes Editor, but the role picker never offers Editor. On a Collect workspace, Editor makes an admin payer's row look editable. The picker then shows only "Workspace admin", the role they already have. So the row is a dead end, and no screen uses common.roleCannotBeChanged.

    What changed since my first proposal:

    • PAYER_ROLES and canRolePay moved to src/libs/PolicyUtils/permissions.ts.
    • A helper now exists, getSelectableRoles(policy, login, allowedRoles). It returns exactly the roles the picker offers, so it already leaves out Editor.
    • The inline Role cell on the Members list repeats the same Editor check, so it has the same dead end. The bulk "Change role" action already uses getSelectableRoles and works correctly.

    On a Control workspace, an admin payer can switch to Payments Admin. There, an editable row with no hint is intended, and the test case should be updated.

    Code references

    What changes do you think we should make in order to solve the problem?

    1. Add a shared helper to PolicyUtils, for example canReimburserChangeRole(policy, login, currentRole). It returns true when getSelectableRoles(policy, login, [...PAYER_ROLES]) contains a role other than currentRole.
    2. Use this helper in WorkspaceMemberDetailsPage and in the inline Role cell in WorkspaceMembersPage, in place of the existing assignablePayerRoles checks.
    3. In WorkspaceMemberDetailsPage, when the member is the payer and has no other role to switch to, disable the row. Show common.roleCannotBeChanged with shouldRenderHintAsHTML, and link it to the Workflows payments tab with useEnvironment and ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID, CONST.TAB.WORKFLOWS.PAYMENTS).
    4. Change the Collect test above so it expects a disabled row with the hint. Add a test for the inline cell, and keep the existing Control tests, which expect an editable row with no hint.
    5. Update the regression test case so it expects the lock and hint only when the payer has no other payer role, for example an admin payer on Collect.

    What alternative solutions did you explore? (Optional)


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


    view run

  15. melvin-bot commented on Nov 6, 2026

    @melvin-bot

    @getusha Whoops! This issue is 2 days overdue. Let's get this updated quick!

  16. jeffrey-graham commented on Nov 7, 2026

    @jeffrey-graham
  17. melvin-bot commented on Nov 7, 2026

    @melvin-bot
  18. getusha commented on Nov 9, 2026

    @getusha
    Contributor

    @MelvinBot's proposal looks good to me
    🎀 👀 🎀 C+ Reviewed

  19. melvin-bot commented on Nov 9, 2026

    @melvin-bot

    Triggered auto assignment to @jasperhuangg, see https://stackoverflow.com/c/expensify/questions/7972 for more details.

  20. MelvinBot commented on Nov 9, 2026

    @MelvinBot
    Contributor

    ⚠️ Something went wrong. This may be a transient issue. Try again. If it persists, check status.openai.com.

    Run details

  21. jasperhuangg commented on Nov 11, 2026

    @jasperhuangg
    Contributor

    @getusha feel free to move forward with implementing Melvin's proposal.

  22. getusha commented on Nov 13, 2026

    @getusha
    Contributor

    @MelvinBot implement this

  23. MelvinBot commented on Nov 13, 2026

    @MelvinBot
    Contributor

    🤖 Web test steps verification results: ❌ fail

    Blocked on dev NewDot web: the existing session is suspended, and page snapshots contain zero nodes. No requested behavior could be verified.

    Blocked app page with no accessible content

    Step Status
    Collect payer role lock, explanation, payer link, and inline Role cellNot verified: session remained suspended after one retry, and the page exposed no content.
    Blocked app page with no accessible content
    ❌
    Control admin payer role remains editable with Payments Admin and no lock hintNot verified due to the same session blocker.
    ❌

    Implemented in draft PR: Disable payer role editing when no selectable alternative exists, assigned to getusha.

    Automated checks passed: 39,477 tests, lint, typecheck, spelling, formatting, targeted React Compiler checks, and Storybook smoke test. Manual web verification remains outstanding.


    view run · no recording available

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions