Skip to content

Approve: spurious LHN green dot after approving (stale hasOutstandingChildRequest on expense report, not cleared optimistically) #93483

Description

@MelvinBot

Problem

After approving an expense report, the green Approve action badge in the LHN disappears (correct) but is immediately replaced by a green dot ("greenlight" / GBR indicator). The green dot should not be there — the report is fully actioned. Navigating away and back (which triggers OpenReport) clears the green dot, confirming the optimistic state is wrong and the server state is right.

Reproduction

  1. Be the approver of an expense report that shows the green Approve badge in the LHN.
  2. Approve the report.
  3. Observe the LHN row: the Approve badge is replaced by a spurious green dot.
  4. Navigate to another report and back (triggers OpenReport).
  5. The green dot disappears.

Expected: After approving, the LHN row shows no action badge and no green dot.
Actual: A green dot lingers until OpenReport refetches server data.

Root cause

The LHN green dot for an expense report is driven by the report's own hasOutstandingChildRequest field. In getReasonAndReportActionThatRequiresAttention:

const hasValidIOUAction =
    ((optionOrReport.hasOutstandingChildRequest === true && !hasStaleChildRequest) || iouReportActionToApproveOrPay?.reportActionID) &&
    (policy?.reimbursementChoice !== CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO || !hasOnlyPendingTransactions);

When hasOutstandingChildRequest === true, this returns HAS_CHILD_REPORT_AWAITING_ACTION, which sets brickRoadStatus = INFO (the green dot) in reportAttributes.ts.

The optimistic approve flow in approveMoneyRequest (ReportWorkflow.ts:480-526) correctly updates stateNum, statusNum, managerID, and nextStep on the expense report, and recomputes hasOutstandingChildRequestbut only on the parent chat report:

const updatedExpenseReport = {
    ...expenseReport,
    // ... stateNum, statusNum, managerID, nextStep ...   <-- no hasOutstandingChildRequest
};
optimisticData.push({ ...key: REPORT${expenseReport.reportID}, value: updatedExpenseReport });

if (chatReport) {
    optimisticData.push({
        key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.chatReportID}`,   // <-- chat report, not expense report
        value: { hasOutstandingChildRequest: hasOutstandingChildRequest(chatReport, updatedExpenseReport, ...) },
    });
}

So the expense report keeps its stale hasOutstandingChildRequest: true until OpenReport returns fresh server data with false.

Evidence (Onyx for the expense report)

reportID: 8367965920096919, type: expense, stateNum: 2, statusNum: 3, non-reimbursable (total: -8710, nonReimbursableTotal: -8710, reimbursableTotal: 0).

Immediately after optimistic approve (buggy — green dot shows):

"hasOutstandingChildRequest": true,
"nextStep": { "icon": "hourglass", "messageKey": "waitingToApprove" }

After navigating away/back — OpenReport (correct — no green dot):

"hasOutstandingChildRequest": false,
"nextStep": { "icon": "checkmark", "messageKey": "noFurtherAction" }

The single field that toggles the green dot here is hasOutstandingChildRequest. (nextStep does not factor into the green-dot or action-badge computation, though note the optimistic nextStep above also lands as stale waitingToApprove rather than the server's noFurtherAction — the nextStep optimistic value is built by buildOptimisticNextStep and should resolve to noFurtherAction for this reimbursableSpend === 0 case; worth confirming during the fix.)

Recommended fix

In approveMoneyRequest, include a recomputed hasOutstandingChildRequest on the expense report's optimistic merge (updatedExpenseReport), not just on the chat report — so the green dot clears optimistically instead of waiting for OpenReport. Add a matching failureData entry to roll it back if the approve fails.

Investigation details
  • Green dot (brickRoadIndicator === INFO) is read by SidebarUtils.getOptionData from the derived reportAttributes.brickRoadStatus (reportAttributes.ts:299-342), which delegates to getReasonAndReportActionThatRequiresAttention in ReportUtils.ts.
  • The green Approve action badge is computed separately by getBadgeFromIOUReport / canApproveIOU (ReportWorkflow.ts); after approval canApproveIOU returns false, so the badge correctly disappears.
  • nextStep is not consumed by either indicator.

Reported in Slack.

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @aimane-chnaif

Metadata

Metadata

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions