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
13 changes: 12 additions & 1 deletion src/libs/DebugUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1487,10 +1487,21 @@ function getReasonAndReportActionForRBRInLHNRow(
transactionViolations: OnyxCollection<TransactionViolation[]>,
hasViolations: boolean,
reportErrors: Errors,
isOffline: boolean,
isArchivedReport = false,
): RBRReasonAndReportAction | null {
const {reason, reportAction} =
SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(report, chatReport, reportActions, hasViolations, reportErrors, transactions, transactionViolations, isArchivedReport) ?? {};
SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(
report,
chatReport,
reportActions,
hasViolations,
reportErrors,
transactions,
isOffline,
transactionViolations,
isArchivedReport,
) ?? {};

if (reason) {
return {reason: `debug.reasonRBR.${reason}`, reportAction};
Expand Down
11 changes: 9 additions & 2 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import type PolicyData from '@hooks/usePolicyData/types';
import type {PolicyTagList} from '@pages/workspace/tags/types';
import type {ThemeColors} from '@styles/theme/types';
import {canApproveIOU, canIOUBePaid, canSubmitReport, getIOUReportActionWithBadge} from '@userActions/IOU/ReportWorkflow';

Check warning on line 27 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Unexpected subpath import via alias '@userActions/IOU/ReportWorkflow'. Use './actions/IOU/ReportWorkflow' instead
import type {IOUAction, IOUType, OnboardingAccounting} from '@src/CONST';
import CONST, {TASK_TO_FEATURE} from '@src/CONST';
import type {ParentNavigationSummaryParams} from '@src/languages/params';
Expand Down Expand Up @@ -1036,7 +1036,7 @@
};

let conciergeReportIDOnyxConnect: OnyxEntry<string>;
Onyx.connect({

Check warning on line 1039 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.CONCIERGE_REPORT_ID,
callback: (value) => {
conciergeReportIDOnyxConnect = value;
Expand All @@ -1044,7 +1044,7 @@
});

const defaultAvatarBuildingIconTestID = 'SvgDefaultAvatarBuilding Icon';
Onyx.connect({

Check warning on line 1047 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
// When signed out, val is undefined
Expand All @@ -1062,7 +1062,7 @@
let allPersonalDetails: OnyxEntry<PersonalDetailsList>;
let allPersonalDetailLogins: string[];
let currentUserPersonalDetails: OnyxEntry<PersonalDetails>;
Onyx.connect({

Check warning on line 1065 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
if (deprecatedCurrentUserAccountID) {
Expand All @@ -1074,7 +1074,7 @@
});

let allReportsDraft: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 1077 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
waitForCollectionCallback: true,
callback: (value) => (allReportsDraft = value),
Expand All @@ -1082,7 +1082,7 @@

let allPolicies: OnyxCollection<Policy>;
let policiesArray: Policy[] = [];
Onyx.connect({

Check warning on line 1085 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -1092,7 +1092,7 @@
});

let allPolicyDrafts: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 1095 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY_DRAFTS,
waitForCollectionCallback: true,
callback: (value) => (allPolicyDrafts = value),
Expand All @@ -1100,7 +1100,7 @@

let deprecatedAllReports: OnyxCollection<Report>;
let deprecatedReportsByPolicyID: ReportByPolicyMap;
Onyx.connect({

Check warning on line 1103 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -1136,14 +1136,14 @@
});

let betaConfiguration: OnyxEntry<BetaConfiguration> = {};
Onyx.connect({

Check warning on line 1139 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.BETA_CONFIGURATION,
callback: (value) => (betaConfiguration = value ?? {}),
});

let deprecatedAllTransactions: OnyxCollection<Transaction> = {};
let deprecatedReportsTransactions: Record<string, Transaction[]> = {};
Onyx.connect({

Check warning on line 1146 in src/libs/ReportUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
Expand Down Expand Up @@ -9464,9 +9464,16 @@
return allReportErrors;
}

function getReceiptUploadErrorReason(report: Report, chatReport: OnyxEntry<Report>, reportActions: OnyxEntry<ReportActions>, transactions: OnyxCollection<Transaction>) {
function getReceiptUploadErrorReason(
report: Report,
chatReport: OnyxEntry<Report>,
reportActions: OnyxEntry<ReportActions>,
transactions: OnyxCollection<Transaction>,
// We'll make it required eventually. Refactor issue: https://github.com/Expensify/App/issues/66407
isOffline?: boolean,
) {
const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
const transactionThreadReportAction = getOneTransactionThreadReportAction(report, chatReport, reportActions ?? []);
const transactionThreadReportAction = getOneTransactionThreadReportAction(report, chatReport, reportActions ?? [], isOffline);
if (transactionThreadReportAction) {
const transactionID = isMoneyRequestAction(transactionThreadReportAction) ? getOriginalMessage(transactionThreadReportAction)?.IOUTransactionID : undefined;
const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
Expand Down
3 changes: 2 additions & 1 deletion src/libs/SidebarUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ function getReasonAndReportActionThatHasRedBrickRoad(
hasViolations: boolean,
reportErrors: Errors,
transactions: OnyxCollection<Transaction>,
isOffline: boolean,
transactionViolations?: OnyxCollection<TransactionViolation[]>,
isReportArchived = false,
reports?: OnyxCollection<Report>,
Expand Down Expand Up @@ -709,7 +710,7 @@ function getReasonAndReportActionThatHasRedBrickRoad(
};
}

return getReceiptUploadErrorReason(report, chatReport, reportActions, transactions);
return getReceiptUploadErrorReason(report, chatReport, reportActions, transactions, isOffline);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/libs/actions/OnyxDerived/configs/reportAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {getIsOffline} from '@libs/NetworkState';
import {computeReportName} from '@libs/ReportNameUtils';
import {generateIsEmptyReport, generateReportAttributes, hasVisibleReportFieldViolations, isArchivedReport, isValidReport} from '@libs/ReportUtils';
import SidebarUtils from '@libs/SidebarUtils';
Expand Down Expand Up @@ -89,11 +90,14 @@ export default createOnyxDerivedValueConfig({
ONYXKEYS.COLLECTION.POLICY_TAGS,
ONYXKEYS.COLLECTION.REPORT_METADATA,
ONYXKEYS.CONCIERGE_REPORT_ID,
ONYXKEYS.NETWORK,
],
compute: (
[reports, preferredLocale, transactionViolations, reportActions, reportNameValuePairs, transactions, personalDetails, session, policies, policyTags],
{currentValue, sourceValues},
) => {
// Read the in-memory offline state directly (NETWORK is a dependency so recompute still fires when it changes).
const isOffline = getIsOffline();
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.

Why do we need to use getIsOffline function? Could we use this value directly here because it has already been added to dependencies

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, we can't use isOffline directly from network onyx state since it's derived from multiple variables. We also use it in useNetwork then I think it's fine to use it here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

// Check if display names changed when personal details are updated
let displayNamesChanged = false;
if (hasKeyTriggeredCompute(ONYXKEYS.PERSONAL_DETAILS_LIST, sourceValues)) {
Expand Down Expand Up @@ -284,6 +288,7 @@ export default createOnyxDerivedValueConfig({
hasAnyViolations || hasFieldViolations,
reportErrors,
transactions,
isOffline,
transactionViolations,
!!isReportArchived,
reports,
Expand Down
5 changes: 4 additions & 1 deletion src/pages/Debug/Report/DebugReportPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Text from '@components/Text';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useStyleUtils from '@hooks/useStyleUtils';
Expand Down Expand Up @@ -60,6 +61,7 @@ function DebugReportPage({
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`);
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const {isOffline} = useNetwork();
const reportAttributesSelector = useCallback((attributes: OnyxEntry<ReportAttributesDerivedValue>) => attributes?.reports?.[reportID], [reportID]);
const [reportAttributes] = useOnyx(
ONYXKEYS.DERIVED.REPORT_ATTRIBUTES,
Expand Down Expand Up @@ -95,6 +97,7 @@ function DebugReportPage({
transactionViolations,
hasViolations,
reportAttributes?.reportErrors ?? {},
isOffline,
isReportArchived,
) ?? {};
const hasRBR = !!reasonRBR;
Expand Down Expand Up @@ -153,7 +156,7 @@ function DebugReportPage({
: undefined,
},
];
}, [report, transactionViolations, isReportArchived, chatReport, reportActions, transactions, reportAttributes?.reportErrors, betas, priorityMode, draftComment, translate]);
}, [report, transactionViolations, isReportArchived, chatReport, reportActions, transactions, reportAttributes?.reportErrors, betas, priorityMode, draftComment, translate, isOffline]);

const icons = useMemoizedLazyExpensifyIcons(['Eye']);

Expand Down
110 changes: 104 additions & 6 deletions tests/unit/DebugUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {renderHook} from '@testing-library/react-native';
import type {OnyxCollection} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
import DateUtils from '@libs/DateUtils';
Expand Down Expand Up @@ -1197,6 +1197,7 @@ describe('DebugUtils', () => {
undefined,
false,
{},
false,
) ?? {};
expect(reportAction).toBeUndefined();
});
Expand Down Expand Up @@ -1260,6 +1261,7 @@ describe('DebugUtils', () => {
undefined,
false,
{},
false,
) ?? {};
expect(reportAction).toBe(undefined);
});
Expand Down Expand Up @@ -1327,8 +1329,16 @@ describe('DebugUtils', () => {
};
const reportErrors = getAllReportErrors(MOCK_CHAT_REPORT, MOCK_CHAT_REPORT_ACTIONS, mockTransactions);
const {reportAction} =
DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_CHAT_REPORT, chatReportR14932, MOCK_CHAT_REPORT_ACTIONS, mockTransactions, undefined, false, reportErrors) ??
{};
DebugUtils.getReasonAndReportActionForRBRInLHNRow(
MOCK_CHAT_REPORT,
chatReportR14932,
MOCK_CHAT_REPORT_ACTIONS,
mockTransactions,
undefined,
false,
reportErrors,
false,
) ?? {};
expect(reportAction).toMatchObject(MOCK_CHAT_REPORT_ACTIONS['1']);
});
it('returns correct report action which is a split bill and has an error', async () => {
Expand Down Expand Up @@ -1400,7 +1410,8 @@ describe('DebugUtils', () => {
};
const reportErrors = getAllReportErrors(MOCK_CHAT_REPORT, MOCK_REPORT_ACTIONS, mockTransactions);
const {reportAction} =
DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_CHAT_REPORT, chatReportR14932, MOCK_REPORT_ACTIONS, mockTransactions, undefined, false, reportErrors) ?? {};
DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_CHAT_REPORT, chatReportR14932, MOCK_REPORT_ACTIONS, mockTransactions, undefined, false, reportErrors, false) ??
{};
expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']);
});
});
Expand Down Expand Up @@ -1458,6 +1469,7 @@ describe('DebugUtils', () => {
undefined,
false,
reportErrors,
false,
) ?? {};
expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['1']);
});
Expand Down Expand Up @@ -1486,7 +1498,8 @@ describe('DebugUtils', () => {

const reportErrors = getAllReportErrors(mockedReport, mockedReportActions, sharedAllTransactions);
const {reason} =
DebugUtils.getReasonAndReportActionForRBRInLHNRow(mockedReport, chatReportR14932, mockedReportActions, sharedAllTransactions, undefined, false, reportErrors) ?? {};
DebugUtils.getReasonAndReportActionForRBRInLHNRow(mockedReport, chatReportR14932, mockedReportActions, sharedAllTransactions, undefined, false, reportErrors, false) ??
{};
expect(reason).toBe('debug.reasonRBR.hasErrors');
});
it('returns correct reason when there are violations', () => {
Expand All @@ -1501,6 +1514,7 @@ describe('DebugUtils', () => {
undefined,
true,
{},
false,
) ?? {};
expect(reason).toBe('debug.reasonRBR.hasViolations');
});
Expand All @@ -1516,6 +1530,7 @@ describe('DebugUtils', () => {
undefined,
true,
{},
false,
true,
) ?? {};
expect(reason).toBe(undefined);
Expand Down Expand Up @@ -1572,9 +1587,92 @@ describe('DebugUtils', () => {
reportID: '1',
} as Transaction,
};
const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, chatReportR14932, {}, violationTransactions, transactionViolations, false, {}) ?? {};
const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, chatReportR14932, {}, violationTransactions, transactionViolations, false, {}, false) ?? {};
expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations');
});
it('forwards isOffline through to SidebarUtils so the live IOU transaction-thread receipt error surfaces only when isOffline=false excludes the deleted pending-delete action', () => {
// Given: an expense report with two IOU actions — one live (with a receipt-errored transaction) and one deleted-with-pending-delete.
const OFFLINE_EXPENSE_REPORT: Report = {
reportID: '1',
type: CONST.REPORT.TYPE.EXPENSE,
chatReportID: '2',
};
const OFFLINE_CHAT_REPORT: Report = {
reportID: '2',
};
const liveTransactionID = 'tx-debug-live';
const deletedTransactionID = 'tx-debug-deleted';
const OFFLINE_REPORT_ACTIONS: OnyxEntry<ReportActions> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
'1': {
reportActionID: '1',
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
actorAccountID: 12345,
created: '2024-08-08 18:20:44.171',
message: [{type: 'TEXT', text: 'live'}],
originalMessage: {
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
IOUTransactionID: liveTransactionID,
amount: 10,
currency: CONST.CURRENCY.USD,
},
} as ReportAction,
// eslint-disable-next-line @typescript-eslint/naming-convention
'2': {
reportActionID: '2',
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
actorAccountID: 12345,
created: '2024-08-08 18:25:44.171',
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
message: [{type: 'TEXT', text: '', html: ''}],
originalMessage: {
type: CONST.IOU.REPORT_ACTION_TYPE.CREATE,
IOUTransactionID: deletedTransactionID,
amount: 20,
currency: CONST.CURRENCY.USD,
},
} as ReportAction,
};
const OFFLINE_TRANSACTIONS: OnyxCollection<Transaction> = {
[`${ONYXKEYS.COLLECTION.TRANSACTION}${liveTransactionID}`]: {
transactionID: liveTransactionID,
amount: 10,
errors: {
someErrorKey: {error: CONST.IOU.RECEIPT_ERROR},
},
} as unknown as Transaction,
[`${ONYXKEYS.COLLECTION.TRANSACTION}${deletedTransactionID}`]: {
transactionID: deletedTransactionID,
amount: 20,
} as unknown as Transaction,
};

const offline = DebugUtils.getReasonAndReportActionForRBRInLHNRow(
OFFLINE_EXPENSE_REPORT,
OFFLINE_CHAT_REPORT,
OFFLINE_REPORT_ACTIONS,
OFFLINE_TRANSACTIONS,
{},
false,
{},
true,
);
const online = DebugUtils.getReasonAndReportActionForRBRInLHNRow(
OFFLINE_EXPENSE_REPORT,
OFFLINE_CHAT_REPORT,
OFFLINE_REPORT_ACTIONS,
OFFLINE_TRANSACTIONS,
{},
false,
{},
false,
);

// Online: deleted pending-delete is skipped → 1 IOU thread → receipt error surfaces.
expect(online?.reason).toBe('debug.reasonRBR.hasErrors');
// Offline: deleted pending-delete is counted → 2 IOU actions → no single thread → no receipt error via that path.
expect(offline).toBeNull();
});
});
});
});
4 changes: 2 additions & 2 deletions tests/unit/OnyxDerivedTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ describe('OnyxDerived', () => {
const transaction = createRandomTransaction(1);

// When the report attributes are recomputed with both report and transaction updates
reportAttributes.compute([reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined], {});
reportAttributes.compute([reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined], {});
const reportAttributesComputedValue = reportAttributes.compute(
[reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined],
[reports, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined],
{
sourceValues: {
[ONYXKEYS.COLLECTION.REPORT]: {
Expand Down
Loading
Loading