Skip to content
Merged
Show file tree
Hide file tree
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
19 changes: 4 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
"react-native-localize": "^3.5.4",
"react-native-nitro-modules": "0.29.4",
"react-native-nitro-sqlite": "9.2.0",
"react-native-onyx": "3.0.57",
"react-native-onyx": "3.0.58",
"react-native-pager-view": "8.0.0",
"react-native-pdf": "7.0.2",
"react-native-permissions": "^5.4.0",
Expand Down
182 changes: 95 additions & 87 deletions src/libs/actions/OnyxDerived/configs/reportAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,95 +236,98 @@ export default createOnyxDerivedValueConfig({
}
}

const reportAttributes = dataToIterate.reduce<ReportAttributesDerivedValue['reports']>((acc, key) => {
// source value sends partial data, so we need an entire report object to do computations
const report = reports[key];

if (!report || !isValidReport(report)) {
const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT, '');
if (acc[reportID]) {
delete acc[reportID];
const reportAttributes = dataToIterate.reduce<ReportAttributesDerivedValue['reports']>(
(acc, key) => {
// source value sends partial data, so we need an entire report object to do computations
const report = reports[key];

if (!report || !isValidReport(report)) {
const reportID = key.replace(ONYXKEYS.COLLECTION.REPORT, '');
if (acc[reportID]) {
delete acc[reportID];
}
return acc;
}
return acc;
}

const chatReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`];
const reportNameValuePair = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`];
const reportActionsList = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`];
const isReportArchived = isArchivedReport(reportNameValuePair);
const {
hasAnyViolations,
requiresAttention,
reportErrors,
oneTransactionThreadReportID,
actionBadge: actionGreenBadge,
actionTargetReportActionID: actionGreenTargetReportActionID,
} = generateReportAttributes({
report,
chatReport,
reportActions,
transactionViolations,
isReportArchived,
allTransactions: transactions,
reports,
});

const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`];
const hasFieldViolations = hasVisibleReportFieldViolations(report, policy);

let brickRoadStatus;
let actionBadge;
let actionTargetReportActionID;
const reasonAndReportAction = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
report,
chatReport,
reportActionsList,
hasAnyViolations || hasFieldViolations,
reportErrors,
transactions,
transactionViolations,
!!isReportArchived,
reports,
);
// if report has errors or violations, show red dot
if (reasonAndReportAction) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
actionBadge = CONST.REPORT.ACTION_BADGE.FIX;
actionTargetReportActionID = reasonAndReportAction.reportAction?.reportActionID;
}
// if report does not have error, check if it should show green dot
if (brickRoadStatus !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && requiresAttention) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
actionBadge = actionGreenBadge;
actionTargetReportActionID = actionGreenTargetReportActionID;
}
const chatReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`];
const reportNameValuePair = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`];
const reportActionsList = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`];
const isReportArchived = isArchivedReport(reportNameValuePair);
const {
hasAnyViolations,
requiresAttention,
reportErrors,
oneTransactionThreadReportID,
actionBadge: actionGreenBadge,
actionTargetReportActionID: actionGreenTargetReportActionID,
} = generateReportAttributes({
report,
chatReport,
reportActions,
transactionViolations,
isReportArchived,
allTransactions: transactions,
reports,
});

const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`];
const hasFieldViolations = hasVisibleReportFieldViolations(report, policy);

let brickRoadStatus;
let actionBadge;
let actionTargetReportActionID;
const reasonAndReportAction = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
report,
chatReport,
reportActionsList,
hasAnyViolations || hasFieldViolations,
reportErrors,
transactions,
transactionViolations,
!!isReportArchived,
reports,
);
// if report has errors or violations, show red dot
if (reasonAndReportAction) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
actionBadge = CONST.REPORT.ACTION_BADGE.FIX;
actionTargetReportActionID = reasonAndReportAction.reportAction?.reportActionID;
}
// if report does not have error, check if it should show green dot
if (brickRoadStatus !== CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR && requiresAttention) {
brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.INFO;
actionBadge = actionGreenBadge;
actionTargetReportActionID = actionGreenTargetReportActionID;
}

acc[report.reportID] = {
reportName: report
? computeReportName({
report,
reports,
policies,
transactions,
allReportNameValuePairs: reportNameValuePairs,
personalDetailsList: personalDetails,
reportActions,
currentUserAccountID: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
currentUserLogin: session?.email ?? '',
allPolicyTags: policyTags,
})
: '',
isEmpty: generateIsEmptyReport(report, isReportArchived),
brickRoadStatus,
requiresAttention,
actionBadge,
actionTargetReportActionID,
reportErrors,
oneTransactionThreadReportID,
};
acc[report.reportID] = {
reportName: report
? computeReportName({
report,
reports,
policies,
transactions,
allReportNameValuePairs: reportNameValuePairs,
personalDetailsList: personalDetails,
reportActions,
currentUserAccountID: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
currentUserLogin: session?.email ?? '',
allPolicyTags: policyTags,
})
: '',
isEmpty: generateIsEmptyReport(report, isReportArchived),
brickRoadStatus,
requiresAttention,
actionBadge,
actionTargetReportActionID,
reportErrors,
oneTransactionThreadReportID,
};

return acc;
}, currentValue?.reports ?? {});
return acc;
},
currentValue?.reports ? {...currentValue.reports} : {},
Comment thread
fabioh8010 marked this conversation as resolved.
);

// Propagate errors from IOU reports to their parent chat reports.
const chatReportIDsWithErrors = new Set<string>();
Expand All @@ -347,8 +350,13 @@ export default createOnyxDerivedValueConfig({
continue;
}

reportAttributes[chatReportID].brickRoadStatus = CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
reportAttributes[chatReportID].actionBadge = CONST.REPORT.ACTION_BADGE.FIX;
// 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],
brickRoadStatus: CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR,
actionBadge: CONST.REPORT.ACTION_BADGE.FIX,
Comment on lines 329 to +358
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

For easy code review, these are only the changes in this file.
Others are prettier diff.

};
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,23 @@ export default createOnyxDerivedValueConfig({
);
}

const reportTransactionsAndViolations = currentValue ?? {};
const reportTransactionsAndViolations = currentValue ? {...currentValue} : {};

// Track which reportID entries have been cloned so we only clone once per reportID.
// This avoids mutating nested objects that are still referenced by the cached value.
const clonedReportIDs = new Set<string>();
const ensureCloned = (id: string) => {
if (clonedReportIDs.has(id) || !reportTransactionsAndViolations[id]) {
return;
}

reportTransactionsAndViolations[id] = {
transactions: {...reportTransactionsAndViolations[id].transactions},
violations: {...reportTransactionsAndViolations[id].violations},
};
clonedReportIDs.add(id);
};

for (const transactionKey of transactionsToProcess) {
const transaction = transactions[transactionKey];
const reportID = transaction?.reportID;
Expand All @@ -38,6 +54,7 @@ export default createOnyxDerivedValueConfig({
const previousReportID = transactionReportIDMapping[transactionKey];

if (previousReportID && previousReportID !== reportID && reportTransactionsAndViolations[previousReportID]) {
ensureCloned(previousReportID);
delete reportTransactionsAndViolations[previousReportID].transactions[transactionKey];
const transactionID = transactionKey.replace(ONYXKEYS.COLLECTION.TRANSACTION, '');
if (transactionID) {
Expand All @@ -59,6 +76,9 @@ export default createOnyxDerivedValueConfig({
transactions: {},
violations: {},
};
clonedReportIDs.add(reportID);
} else {
ensureCloned(reportID);
}

const transactionID = transaction.transactionID;
Expand Down
17 changes: 13 additions & 4 deletions src/libs/actions/OnyxDerived/configs/visibleReportActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import ONYXKEYS from '@src/ONYXKEYS';
import type {ReportAction, ReportActions} from '@src/types/onyx';
import type {VisibleReportActionsDerivedValue} from '@src/types/onyx/DerivedValues';

function getOrCreateReportVisibilityRecord(result: VisibleReportActionsDerivedValue, reportID: string): Record<string, boolean> {
function getOrCreateReportVisibilityRecord(result: VisibleReportActionsDerivedValue, reportID: string, clonedReportIDs: Set<string>): Record<string, boolean> {
if (!result[reportID]) {
// Parameter reassignment is necessary here because we are building up the derived value
// object incrementally as we process report actions. Creating a new object would break
// the reference chain and lose previously computed visibility data.
// eslint-disable-next-line no-param-reassign
result[reportID] = {};
clonedReportIDs.add(reportID);
} else if (!clonedReportIDs.has(reportID)) {
// Clone the existing entry to avoid mutating the cached value
// eslint-disable-next-line no-param-reassign
result[reportID] = {...result[reportID]};
clonedReportIDs.add(reportID);
}
return result[reportID];
}
Expand Down Expand Up @@ -50,6 +56,9 @@ export default createOnyxDerivedValueConfig({
const sessionUpdates = sourceValues?.[ONYXKEYS.SESSION];
const networkUpdates = sourceValues?.[ONYXKEYS.NETWORK];

// Track which reportID entries have been cloned to avoid mutating cached nested objects.
const clonedReportIDs = new Set<string>();

// Session change = user changed, need full recompute due to whisper targeting
// Network change = online/offline status changed, need full recompute for DELETE action visibility
if (sessionUpdates || networkUpdates) {
Expand All @@ -61,7 +70,7 @@ export default createOnyxDerivedValueConfig({
}

const reportID = reportActionsKey.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, '');
const reportVisibility = getOrCreateReportVisibilityRecord(result, reportID);
const reportVisibility = getOrCreateReportVisibilityRecord(result, reportID, clonedReportIDs);

for (const [actionID, action] of Object.entries(reportActions)) {
if (action) {
Expand Down Expand Up @@ -89,7 +98,7 @@ export default createOnyxDerivedValueConfig({
}

const reportID = reportActionsKey.replace(ONYXKEYS.COLLECTION.REPORT_ACTIONS, '');
const reportVisibility = getOrCreateReportVisibilityRecord(result, reportID);
const reportVisibility = getOrCreateReportVisibilityRecord(result, reportID, clonedReportIDs);

for (const [actionID, action] of Object.entries(reportActions)) {
if (!action) {
Expand Down Expand Up @@ -129,7 +138,7 @@ export default createOnyxDerivedValueConfig({
continue;
}

const reportVisibility = getOrCreateReportVisibilityRecord(result, reportID);
const reportVisibility = getOrCreateReportVisibilityRecord(result, reportID, clonedReportIDs);

const specificUpdates = reportActionsUpdates?.[reportActionsKey];
const actionIDsToProcess = specificUpdates ? Object.keys(specificUpdates) : Object.keys(reportActions);
Expand Down
Loading