Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 71 additions & 12 deletions src/libs/actions/OnyxDerived/configs/reportAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder';
import {getIsOffline} from '@libs/NetworkState';
import {getLinkedTransactionID} from '@libs/ReportActionsUtils';
import {computeReportName} from '@libs/ReportNameUtils';
import {generateIsEmptyReport, generateReportAttributes, hasVisibleReportFieldViolations, isArchivedReport, isPolicyAdmin, isPolicyExpenseChat, isValidReport} from '@libs/ReportUtils';
import {
generateIsEmptyReport,
generateReportAttributes,
hasViolations,
hasVisibleReportFieldViolations,
isArchivedReport,
isOpenReport,
isPolicyAdmin,
isPolicyExpenseChat,
isProcessingReport,
isValidReport,
} from '@libs/ReportUtils';
import SidebarUtils from '@libs/SidebarUtils';
import createOnyxDerivedValueConfig from '@userActions/OnyxDerived/createOnyxDerivedValueConfig';
import {hasKeyTriggeredCompute} from '@userActions/OnyxDerived/utils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList, Policy, ReportAttributesDerivedValue} from '@src/types/onyx';
import type {PersonalDetailsList, Policy, Report, ReportAttributesDerivedValue} from '@src/types/onyx';

let previousDisplayNames: Record<string, string | undefined> = {};
let previousPersonalDetails: OnyxEntry<PersonalDetailsList> | undefined;
Expand Down Expand Up @@ -374,39 +386,86 @@ export default createOnyxDerivedValueConfig({
);

// Propagate errors from IOU reports to their parent chat reports.
const chatReportIDsWithErrors = new Set<string>();
const currentUserAccountID = session?.accountID ?? CONST.DEFAULT_NUMBER_ID;
const currentUserEmail = session?.email ?? '';
const erroredChildReportIDsByChat = new Map<string, string[]>();
const childReportIDsByChat = new Map<string, string[]>();
for (const report of Object.values(reports)) {
if (!report?.reportID) {
if (!report?.reportID || !report.chatReportID || report.reportID === report.chatReportID) {
continue;
}

const childReportIDs = childReportIDsByChat.get(report.chatReportID) ?? [];
childReportIDs.push(report.reportID);
childReportIDsByChat.set(report.chatReportID, childReportIDs);

// If this is an IOU report and its calculated attributes have an error,
// then we need to mark its parent chat report.
// We read `needsParentChatErrorPropagation` rather than `brickRoadStatus` because the per-report
// pass suppresses the child's own brickRoadStatus when the parent workspace chat is accessible —
// we still need to propagate the error up so the parent shows the indicator.
const attributes = reportAttributes[report.reportID];
if (
report.chatReportID &&
report.reportID !== report.chatReportID &&
(attributes?.needsParentChatErrorPropagation || attributes?.brickRoadStatus === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR)
) {
chatReportIDsWithErrors.add(report.chatReportID);
if (attributes?.needsParentChatErrorPropagation || attributes?.brickRoadStatus === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR) {
const erroredChildReportIDs = erroredChildReportIDsByChat.get(report.chatReportID) ?? [];
erroredChildReportIDs.push(report.reportID);
erroredChildReportIDsByChat.set(report.chatReportID, erroredChildReportIDs);
}
}

// Apply the error status to the parent chat reports.
for (const chatReportID of chatReportIDsWithErrors) {
for (const [chatReportID, erroredChildReportIDs] of erroredChildReportIDsByChat) {
if (!reportAttributes[chatReportID]) {
continue;
}

const chatAttributes = reportAttributes[chatReportID];
let actionTargetReportActionID = chatAttributes.actionTargetReportActionID;

// Returns the report-preview action ID of the oldest child in `reportIDs` matching `predicate`
// (oldest by preview-action creation time), or undefined when none match.
const getOldestPreviewActionID = (reportIDs: string[] | undefined, predicate?: (childReport: OnyxEntry<Report>) => boolean) => {
let oldestCreated: string | undefined;
let targetReportActionID: string | undefined;
for (const childReportID of reportIDs ?? []) {
if (predicate && !predicate(reports[`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`])) {
continue;
}
const reportPreviewAction = getReportPreviewAction(chatReportID, childReportID);

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 Use the derived reportActions snapshot for preview lookup

When this compute is triggered by a REPORT_ACTIONS update, the reportActions dependency is already the fresh snapshot, but calling getReportPreviewAction without its chatReportActions argument falls back to MoneyRequestBuilder's module-level Onyx cache. If that cache has not observed the newly added or updated REPORT_PREVIEW yet, the propagated Fix badge keeps the previous target (often an Approve target) or no target, so pressing the floating badge can still scroll to the wrong item. Pass the current reportActions?.[${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}] snapshot into the helper.

Useful? React with 👍 / 👎.

if (!reportPreviewAction) {
continue;
}
if (oldestCreated === undefined || reportPreviewAction.created < oldestCreated) {
oldestCreated = reportPreviewAction.created;
targetReportActionID = reportPreviewAction.reportActionID;
}
}
return targetReportActionID;
};

const isActionable = (childReport: OnyxEntry<Report>) => isOpenReport(childReport) || isProcessingReport(childReport);
// Open/processing and the user still needs to fix a violation on it. Violations are read directly from
// transactionViolations so this works even when owner data is absent (e.g. masked Onyx exports).
const needsViolationFix = (childReport: OnyxEntry<Report>) => {
if (!childReport || !isActionable(childReport)) {
return false;
}
const childPolicy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${childReport.policyID}`];
return hasViolations(childReport.reportID, transactionViolations, currentUserAccountID, currentUserEmail, true, undefined, childReport, childPolicy);
};

actionTargetReportActionID =
getOldestPreviewActionID(erroredChildReportIDs, isActionable) ??
getOldestPreviewActionID(childReportIDsByChat.get(chatReportID), needsViolationFix) ??
getOldestPreviewActionID(erroredChildReportIDs) ??
actionTargetReportActionID;

// Clone the entry before mutating — it may be a reference carried over from
// currentValue.reports that wasn't recomputed in this incremental run.
reportAttributes[chatReportID] = {
...reportAttributes[chatReportID],
...chatAttributes,
brickRoadStatus: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR,
actionBadge: CONST.REPORT.ACTION_BADGE.FIX,
actionTargetReportActionID,
};
}

Expand Down
Loading