From 6819a114d9f8251d4b3fb4517b07dc0103a70f50 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 15 Oct 2024 20:38:42 +0100 Subject: [PATCH 01/12] feat(debug mode): add RBR reason to LHN debugging --- src/CONST.ts | 6 + src/languages/en.ts | 5 + src/languages/es.ts | 5 + src/libs/DebugUtils.ts | 18 +- src/libs/SidebarUtils.ts | 40 +- src/pages/Debug/Report/DebugReportPage.tsx | 9 +- tests/unit/DebugUtilsTest.ts | 536 +++++++++++---------- 7 files changed, 343 insertions(+), 276 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index ac72000c5c0a..9c758a4871fc 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5891,6 +5891,12 @@ const CONST = { HAS_CHILD_REPORT_AWAITING_ACTION: 'hasChildReportAwaitingAction', HAS_MISSING_INVOICE_BANK_ACCOUNT: 'hasMissingInvoiceBankAccount', }, + + RBR_REASONS: { + HAS_ERRORS: 'hasErrors', + HAS_VIOLATIONS: 'hasViolations', + HAS_TRANSACTION_THREAD_VIOLATIONS: 'hasTransactionThreadViolations', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/languages/en.ts b/src/languages/en.ts index 287f36b8b040..f749d82e2aa5 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5070,6 +5070,11 @@ const translations = { hasChildReportAwaitingAction: 'Has child report awaiting action', hasMissingInvoiceBankAccount: 'Has missing invoice bank account', }, + reasonRBR: { + hasErrors: 'Has errors', + hasViolations: 'Has violations', + hasTransactionThreadViolations: 'Has transaction thread violations', + }, }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 6e6b7fa9d1d6..26c9954eb3a2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5585,6 +5585,11 @@ const translations = { hasChildReportAwaitingAction: 'Informe secundario pendiente de acción', hasMissingInvoiceBankAccount: 'Falta la cuenta bancaria de la factura', }, + reasonRBR: { + hasErrors: 'Tiene errores', + hasViolations: 'Tiene violaciones', + hasTransactionThreadViolations: 'Tiene violaciones de hilo de transacciones', + }, }, }; diff --git a/src/libs/DebugUtils.ts b/src/libs/DebugUtils.ts index 939bd3d1aa10..123e6215da10 100644 --- a/src/libs/DebugUtils.ts +++ b/src/libs/DebugUtils.ts @@ -8,6 +8,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Beta, Policy, Report, ReportAction, ReportActions, TransactionViolation} from '@src/types/onyx'; import * as OptionsListUtils from './OptionsListUtils'; import * as ReportUtils from './ReportUtils'; +import SidebarUtils from './SidebarUtils'; class NumberError extends SyntaxError { constructor() { @@ -641,13 +642,22 @@ function getReasonAndReportActionForGBRInLHNRow(report: OnyxEntry): GBRR return null; } +type RBRReasonAndReportAction = { + reason: TranslationPaths; + reportAction: OnyxEntry; +}; + /** * Gets the report action that is causing the RBR to show up in LHN */ -function getRBRReportAction(report: OnyxEntry, reportActions: OnyxEntry): OnyxEntry { - const {reportAction} = OptionsListUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); +function getReasonAndReportActionForRBRInLHNRow(report: Report, reportActions: OnyxEntry, hasViolations: boolean): RBRReasonAndReportAction | null { + const {reason, reportAction} = SidebarUtils.getReasonAndReportActionThatHasRedBrickRoad(report, reportActions, hasViolations, transactionViolations) ?? {}; - return reportAction; + if (reason) { + return {reason: `debug.reasonRBR.${reason}`, reportAction}; + } + + return null; } const DebugUtils = { @@ -669,7 +679,7 @@ const DebugUtils = { validateReportActionJSON, getReasonForShowingRowInLHN, getReasonAndReportActionForGBRInLHNRow, - getRBRReportAction, + getReasonAndReportActionForRBRInLHNRow, REPORT_ACTION_REQUIRED_PROPERTIES, REPORT_REQUIRED_PROPERTIES, }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index eb5b3c58cdef..285b20a9918e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,7 @@ import {Str} from 'expensify-common'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {PolicySelector, ReportActionsSelector} from '@hooks/useReportIDs'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -234,10 +235,21 @@ function getOrderedReportIDs( return LHNReports; } -function shouldShowRedBrickRoad(report: Report, reportActions: OnyxEntry, hasViolations: boolean, transactionViolations?: OnyxCollection) { - const hasErrors = Object.keys(OptionsListUtils.getAllReportErrors(report, reportActions)).length !== 0; +type ReasonAndReportActionThatHasRedBrickRoad = { + reason: ValueOf; + reportAction?: OnyxEntry; +}; +function getReasonAndReportActionThatHasRedBrickRoad( + report: Report, + reportActions: OnyxEntry, + hasViolations: boolean, + transactionViolations?: OnyxCollection, +): ReasonAndReportActionThatHasRedBrickRoad | null { + const {errors, reportAction} = OptionsListUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions); + const hasErrors = Object.keys(errors).length !== 0; const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, ReportActionsUtils.getAllReportActions(report.reportID)); + if (oneTransactionThreadReportID) { const oneTransactionThreadReport = ReportUtils.getReport(oneTransactionThreadReportID); @@ -248,11 +260,30 @@ function shouldShowRedBrickRoad(report: Report, reportActions: OnyxEntry, hasViolations: boolean, transactionViolations?: OnyxCollection) { + return !!getReasonAndReportActionThatHasRedBrickRoad(report, reportActions, hasViolations, transactionViolations); } /** @@ -630,5 +661,6 @@ export default { getOptionData, getOrderedReportIDs, getWelcomeMessage, + getReasonAndReportActionThatHasRedBrickRoad, shouldShowRedBrickRoad, }; diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index 530b4b5f4aec..aae4481edfae 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -17,7 +17,6 @@ import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import type {DebugParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; -import SidebarUtils from '@libs/SidebarUtils'; import DebugDetails from '@pages/Debug/DebugDetails'; import DebugJSON from '@pages/Debug/DebugJSON'; import Debug from '@userActions/Debug'; @@ -60,11 +59,12 @@ function DebugReportPage({ } const reasonLHN = DebugUtils.getReasonForShowingRowInLHN(report); - const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; - const reportActionRBR = DebugUtils.getRBRReportAction(report, reportActions); const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID); - const hasRBR = SidebarUtils.shouldShowRedBrickRoad(report, reportActions, !!shouldDisplayViolations || shouldDisplayReportViolations, transactionViolations); + const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations; + const {reason: reasonGBR, reportAction: reportActionGBR} = DebugUtils.getReasonAndReportActionForGBRInLHNRow(report) ?? {}; + const {reason: reasonRBR, reportAction: reportActionRBR} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, reportActions, hasViolations) ?? {}; + const hasRBR = !!reasonRBR; const hasGBR = !hasRBR && !!reasonGBR; return [ @@ -94,6 +94,7 @@ function DebugReportPage({ { title: translate('debug.RBR'), subtitle: translate(`debug.${hasRBR}`), + message: hasRBR ? translate(reasonRBR) : undefined, action: hasRBR && reportActionRBR ? { diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 34c2ad2bde73..ef85eb317770 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -945,83 +945,265 @@ describe('DebugUtils', () => { expect(reportAction).toBeUndefined(); }); }); - describe('getRBRReportAction', () => { + describe('getReasonAndReportActionForRBRInLHNRow', () => { beforeAll(() => { Onyx.init({ keys: ONYXKEYS, }); }); - beforeEach(() => { - Onyx.clear(); - }); - it('returns undefined when report has no RBR', () => { - const reportAction = DebugUtils.getRBRReportAction( - { - reportID: '1', - }, - undefined, - ); - expect(reportAction).toBeUndefined(); - }); - // TODO: remove '.failing' once the implementation is fixed - it.failing('returns parentReportAction if it is a transaction thread, the transaction is missing smart scan fields and the report is not settled', async () => { - const MOCK_REPORTS: ReportCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: { - reportID: '1', - parentReportID: '2', - parentReportActionID: '1', - statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, - }, - [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: { - reportID: '2', - }, - }; - const MOCK_REPORT_ACTIONS: ReportActionsCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2` as const]: { - // 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: CONST.IOU.REPORT_ACTION_TYPE.CREATE, - amount: 10, - currency: CONST.CURRENCY.USD, - expenseReportID: '1', - text: 'Vacation expense', - IOUTransactionID: '1', + describe('reportAction', () => { + beforeEach(() => { + Onyx.clear(); + }); + it('returns undefined when report has no RBR', () => { + const {reportAction} = + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + { + reportID: '1', }, + undefined, + false, + ) ?? {}; + expect(reportAction).toBeUndefined(); + }); + // TODO: remove '.failing' once the implementation is fixed + it.failing('returns parentReportAction if it is a transaction thread, the transaction is missing smart scan fields and the report is not settled', async () => { + const MOCK_REPORTS: ReportCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: { + reportID: '1', + parentReportID: '2', + parentReportActionID: '1', + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }, - }, - }; - await Onyx.multiSet({ - ...MOCK_REPORTS, - ...MOCK_REPORT_ACTIONS, - [ONYXKEYS.SESSION]: { - accountID: 12345, - }, - [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, - }, + [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: { + reportID: '2', + }, + }; + const MOCK_REPORT_ACTIONS: ReportActionsCollectionDataSet = { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2` as const]: { + // 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: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + amount: 10, + currency: CONST.CURRENCY.USD, + expenseReportID: '1', + text: 'Vacation expense', + IOUTransactionID: '1', + }, + }, + }, + }; + await Onyx.multiSet({ + ...MOCK_REPORTS, + ...MOCK_REPORT_ACTIONS, + [ONYXKEYS.SESSION]: { + accountID: 12345, + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { + amount: 0, + modifiedAmount: 0, + }, + }); + const {reportAction} = + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style + MOCK_REPORTS[`${ONYXKEYS.COLLECTION.REPORT}1`] as Report, + undefined, + false, + ) ?? {}; + expect(reportAction).toBe(1); }); - const reportAction = DebugUtils.getRBRReportAction( - // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style - MOCK_REPORTS[`${ONYXKEYS.COLLECTION.REPORT}1`] as Report, - undefined, - ); - expect(reportAction).toBe(1); - }); - describe("Report has missing fields, isn't settled and it's owner is the current user", () => { - describe('Report is IOU', () => { - it('returns correct report action which has missing fields', async () => { - const MOCK_IOU_REPORT: Report = { + describe("Report has missing fields, isn't settled and it's owner is the current user", () => { + describe('Report is IOU', () => { + it('returns correct report action which has missing fields', async () => { + const MOCK_IOU_REPORT: Report = { + reportID: '1', + type: CONST.REPORT.TYPE.IOU, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + ownerAccountID: 12345, + }; + const MOCK_REPORT_ACTIONS: ReportActions = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0': { + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + reportActionID: '0', + created: '2024-08-08 18:20:44.171', + } as ReportAction<'CREATED'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '1': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '1', + message: { + IOUTransactionID: '2', + }, + actorAccountID: 1, + } as ReportAction<'IOU'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '2': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '2', + message: { + IOUTransactionID: '1', + }, + actorAccountID: 1, + } as ReportAction<'IOU'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '3': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '3', + message: { + IOUTransactionID: '1', + }, + actorAccountID: 12345, + } as ReportAction<'IOU'>, + }; + await Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { + amount: 0, + modifiedAmount: 0, + }, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, + [ONYXKEYS.SESSION]: { + accountID: 12345, + }, + }); + const {reportAction} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_IOU_REPORT, MOCK_REPORT_ACTIONS, false) ?? {}; + expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); + }); + }); + describe('Report is expense', () => { + it('returns correct report action which has missing fields', async () => { + const MOCK_IOU_REPORT: Report = { + reportID: '1', + type: CONST.REPORT.TYPE.EXPENSE, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, + ownerAccountID: 12345, + }; + const MOCK_REPORT_ACTIONS: ReportActions = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0': { + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + reportActionID: '0', + created: '2024-08-08 18:20:44.171', + } as ReportAction<'CREATED'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '1': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '1', + message: { + IOUTransactionID: '2', + }, + actorAccountID: 1, + } as ReportAction<'IOU'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '2': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '2', + message: { + IOUTransactionID: '1', + }, + actorAccountID: 1, + } as ReportAction<'IOU'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '3': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '3', + message: { + IOUTransactionID: '1', + }, + actorAccountID: 12345, + } as ReportAction<'IOU'>, + }; + await Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { + amount: 0, + modifiedAmount: 0, + }, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, + [ONYXKEYS.SESSION]: { + accountID: 12345, + }, + }); + const {reportAction} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_IOU_REPORT, MOCK_REPORT_ACTIONS, false) ?? {}; + expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); + }); + }); + }); + describe('There is a report action with smart scan errors', () => { + it('returns correct report action which is a report preview and has an error', async () => { + const MOCK_CHAT_REPORT: Report = { reportID: '1', + type: CONST.REPORT.TYPE.CHAT, + ownerAccountID: 12345, + }; + const MOCK_IOU_REPORT: Report = { + reportID: '2', type: CONST.REPORT.TYPE.IOU, statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, ownerAccountID: 12345, }; + const MOCK_CHAT_REPORT_ACTIONS: ReportActions = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0': { + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + reportActionID: '0', + created: '2024-08-08 18:20:44.171', + } as ReportAction<'CREATED'>, + // eslint-disable-next-line @typescript-eslint/naming-convention + '1': { + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + reportActionID: '3', + message: { + linkedReportID: '2', + }, + actorAccountID: 1, + } as ReportAction<'REPORTPREVIEW'>, + }; + const MOCK_IOU_REPORT_ACTIONS = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '1': { + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + reportActionID: '1', + message: { + IOUTransactionID: '1', + }, + actorAccountID: 12345, + } as ReportAction<'IOU'>, + }; + await Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { + amount: 0, + modifiedAmount: 0, + }, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_CHAT_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: MOCK_IOU_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_CHAT_REPORT_ACTIONS, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2` as const]: MOCK_IOU_REPORT_ACTIONS, + [ONYXKEYS.SESSION]: { + accountID: 12345, + }, + }); + const {reportAction} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_CHAT_REPORT, MOCK_CHAT_REPORT_ACTIONS, false) ?? {}; + expect(reportAction).toMatchObject(MOCK_CHAT_REPORT_ACTIONS['1']); + }); + it('returns correct report action which is a split bill and has an error', async () => { + const MOCK_CHAT_REPORT: Report = { + reportID: '1', + type: CONST.REPORT.TYPE.CHAT, + ownerAccountID: 1, + }; + const MOCK_IOU_REPORT: Report = { + reportID: '2', + type: CONST.REPORT.TYPE.IOU, + ownerAccountID: 1, + }; const MOCK_REPORT_ACTIONS: ReportActions = { // eslint-disable-next-line @typescript-eslint/naming-convention '0': { @@ -1053,8 +1235,9 @@ describe('DebugUtils', () => { reportActionID: '3', message: { IOUTransactionID: '1', + type: CONST.IOU.REPORT_ACTION_TYPE.SPLIT, }, - actorAccountID: 12345, + actorAccountID: 1, } as ReportAction<'IOU'>, }; await Onyx.multiSet({ @@ -1062,18 +1245,17 @@ describe('DebugUtils', () => { amount: 0, modifiedAmount: 0, }, - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_CHAT_REPORT, + [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: MOCK_IOU_REPORT, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, [ONYXKEYS.SESSION]: { accountID: 12345, }, }); - const reportAction = DebugUtils.getRBRReportAction(MOCK_IOU_REPORT, MOCK_REPORT_ACTIONS); + const {reportAction} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_CHAT_REPORT, MOCK_REPORT_ACTIONS, false) ?? {}; expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); }); - }); - describe('Report is expense', () => { - it('returns correct report action which has missing fields', async () => { + it("returns undefined if there's no report action is a report preview or a split bill", async () => { const MOCK_IOU_REPORT: Report = { reportID: '1', type: CONST.REPORT.TYPE.EXPENSE, @@ -1126,218 +1308,44 @@ describe('DebugUtils', () => { accountID: 12345, }, }); - const reportAction = DebugUtils.getRBRReportAction(MOCK_IOU_REPORT, MOCK_REPORT_ACTIONS); + const {reportAction} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(MOCK_IOU_REPORT, MOCK_REPORT_ACTIONS, false) ?? {}; expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); }); }); - }); - describe('There is a report action with smart scan errors', () => { - it('returns correct report action which is a report preview and has an error', async () => { - const MOCK_CHAT_REPORT: Report = { - reportID: '1', - type: CONST.REPORT.TYPE.CHAT, - ownerAccountID: 12345, - }; - const MOCK_IOU_REPORT: Report = { - reportID: '2', - type: CONST.REPORT.TYPE.IOU, - statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, - ownerAccountID: 12345, - }; - const MOCK_CHAT_REPORT_ACTIONS: ReportActions = { - // eslint-disable-next-line @typescript-eslint/naming-convention - '0': { - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - reportActionID: '0', - created: '2024-08-08 18:20:44.171', - } as ReportAction<'CREATED'>, - // eslint-disable-next-line @typescript-eslint/naming-convention - '1': { - actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, - reportActionID: '3', - message: { - linkedReportID: '2', - }, - actorAccountID: 1, - } as ReportAction<'REPORTPREVIEW'>, - }; - const MOCK_IOU_REPORT_ACTIONS = { - // eslint-disable-next-line @typescript-eslint/naming-convention - '1': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - reportActionID: '1', - message: { - IOUTransactionID: '1', - }, - actorAccountID: 12345, - } as ReportAction<'IOU'>, - }; - await Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, - }, - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_CHAT_REPORT, - [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: MOCK_IOU_REPORT, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_CHAT_REPORT_ACTIONS, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2` as const]: MOCK_IOU_REPORT_ACTIONS, - [ONYXKEYS.SESSION]: { - accountID: 12345, - }, - }); - const reportAction = DebugUtils.getRBRReportAction(MOCK_CHAT_REPORT, MOCK_CHAT_REPORT_ACTIONS); - expect(reportAction).toMatchObject(MOCK_CHAT_REPORT_ACTIONS['1']); - }); - it('returns correct report action which is a split bill and has an error', async () => { - const MOCK_CHAT_REPORT: Report = { - reportID: '1', - type: CONST.REPORT.TYPE.CHAT, - ownerAccountID: 1, - }; - const MOCK_IOU_REPORT: Report = { - reportID: '2', - type: CONST.REPORT.TYPE.IOU, - ownerAccountID: 1, - }; + it('returns report action that contains errors', () => { const MOCK_REPORT_ACTIONS: ReportActions = { // eslint-disable-next-line @typescript-eslint/naming-convention '0': { - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, reportActionID: '0', - created: '2024-08-08 18:20:44.171', - } as ReportAction<'CREATED'>, + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + created: '2024-08-08 18:40:44.171', + }, // eslint-disable-next-line @typescript-eslint/naming-convention '1': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, reportActionID: '1', - message: { - IOUTransactionID: '2', + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, + created: '2024-08-08 18:42:44.171', + errors: { + randomError: 'Random error', }, - actorAccountID: 1, - } as ReportAction<'IOU'>, + }, // eslint-disable-next-line @typescript-eslint/naming-convention '2': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, reportActionID: '2', - message: { - IOUTransactionID: '1', - }, - actorAccountID: 1, - } as ReportAction<'IOU'>, - // eslint-disable-next-line @typescript-eslint/naming-convention - '3': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - reportActionID: '3', - message: { - IOUTransactionID: '1', - type: CONST.IOU.REPORT_ACTION_TYPE.SPLIT, - }, - actorAccountID: 1, - } as ReportAction<'IOU'>, - }; - await Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, + created: '2024-08-08 18:44:44.171', }, - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_CHAT_REPORT, - [`${ONYXKEYS.COLLECTION.REPORT}2` as const]: MOCK_IOU_REPORT, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, - [ONYXKEYS.SESSION]: { - accountID: 12345, - }, - }); - const reportAction = DebugUtils.getRBRReportAction(MOCK_CHAT_REPORT, MOCK_REPORT_ACTIONS); - expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); - }); - it("returns undefined if there's no report action is a report preview or a split bill", async () => { - const MOCK_IOU_REPORT: Report = { - reportID: '1', - type: CONST.REPORT.TYPE.EXPENSE, - statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, - ownerAccountID: 12345, }; - const MOCK_REPORT_ACTIONS: ReportActions = { - // eslint-disable-next-line @typescript-eslint/naming-convention - '0': { - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - reportActionID: '0', - created: '2024-08-08 18:20:44.171', - } as ReportAction<'CREATED'>, - // eslint-disable-next-line @typescript-eslint/naming-convention - '1': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - reportActionID: '1', - message: { - IOUTransactionID: '2', - }, - actorAccountID: 1, - } as ReportAction<'IOU'>, - // eslint-disable-next-line @typescript-eslint/naming-convention - '2': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - reportActionID: '2', - message: { - IOUTransactionID: '1', - }, - actorAccountID: 1, - } as ReportAction<'IOU'>, - // eslint-disable-next-line @typescript-eslint/naming-convention - '3': { - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - reportActionID: '3', - message: { - IOUTransactionID: '1', + const {reportAction} = + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + { + reportID: '1', }, - actorAccountID: 12345, - } as ReportAction<'IOU'>, - }; - await Onyx.multiSet({ - [`${ONYXKEYS.COLLECTION.TRANSACTION}1` as const]: { - amount: 0, - modifiedAmount: 0, - }, - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: MOCK_IOU_REPORT, - [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1` as const]: MOCK_REPORT_ACTIONS, - [ONYXKEYS.SESSION]: { - accountID: 12345, - }, - }); - const reportAction = DebugUtils.getRBRReportAction(MOCK_IOU_REPORT, MOCK_REPORT_ACTIONS); - expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['3']); + MOCK_REPORT_ACTIONS, + false, + ) ?? {}; + expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['1']); }); }); - it('returns report action that contains errors', () => { - const MOCK_REPORT_ACTIONS: ReportActions = { - // eslint-disable-next-line @typescript-eslint/naming-convention - '0': { - reportActionID: '0', - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - created: '2024-08-08 18:40:44.171', - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '1': { - reportActionID: '1', - actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, - created: '2024-08-08 18:42:44.171', - errors: { - randomError: 'Random error', - }, - }, - // eslint-disable-next-line @typescript-eslint/naming-convention - '2': { - reportActionID: '2', - actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, - created: '2024-08-08 18:44:44.171', - }, - }; - const reportAction = DebugUtils.getRBRReportAction( - { - reportID: '1', - }, - MOCK_REPORT_ACTIONS, - ); - expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['1']); - }); }); }); From fd8cdec6f8c5e650e24042d761fdb9e614dcd70d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 16 Oct 2024 23:44:40 +0100 Subject: [PATCH 02/12] chore(debug mode): add RBR reason unit tests --- tests/unit/DebugUtilsTest.ts | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 35e68221dc08..8a2bb30ed38e 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -1475,5 +1475,87 @@ describe('DebugUtils', () => { expect(reportAction).toMatchObject(MOCK_REPORT_ACTIONS['1']); }); }); + describe('reason', () => { + it('returns correct reason when there are errors', () => { + const {reason} = + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + { + reportID: '1', + }, + { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + reportActionID: '1', + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + created: '2024-09-20 13:11:11.122', + errors: { + randomError: 'Something went wrong', + }, + }, + }, + false, + ) ?? {}; + expect(reason).toBe('debug.reasonRBR.hasErrors'); + }); + it('returns correct reason when there are violations', () => { + const {reason} = + DebugUtils.getReasonAndReportActionForRBRInLHNRow( + { + reportID: '1', + }, + undefined, + true, + ) ?? {}; + expect(reason).toBe('debug.reasonRBR.hasViolations'); + }); + it('returns correct reason when there are transaction thread violations', async () => { + const threadReport: Report = { + reportID: '1', + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, + reportName: 'My first chat', + lastMessageText: 'Hello World!', + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + parentReportID: '0', + parentReportActionID: '0', + }; + const reportActions: ReportActions = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0': { + reportActionID: '0', + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + message: { + type: CONST.IOU.REPORT_ACTION_TYPE.CREATE, + IOUTransactionID: '0', + IOUReportID: '0', + amount: 10, + currency: CONST.CURRENCY.USD, + text: '', + }, + created: '2024-07-13 06:02:11.111', + }, + }; + await Onyx.multiSet({ + [ONYXKEYS.SESSION]: { + accountID: 1234, + }, + [`${ONYXKEYS.COLLECTION.REPORT}0` as const]: { + reportID: '0', + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: 1234, + }, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}0` as const]: reportActions, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: threadReport, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}0` as const]: [ + { + type: CONST.VIOLATION_TYPES.VIOLATION, + name: CONST.VIOLATIONS.MODIFIED_AMOUNT, + }, + ], + }); + const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(threadReport, reportActions, false) ?? {}; + expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations'); + }); + }); }); }); From e9d9106521cd0e46f49cc3378786bbb8a824ca19 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 17 Oct 2024 12:49:55 +0100 Subject: [PATCH 03/12] fix(debug mode): RBR reason unit test for transaction thread violations --- tests/unit/DebugUtilsTest.ts | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 8a2bb30ed38e..6e3b41281991 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -1508,16 +1508,10 @@ describe('DebugUtils', () => { expect(reason).toBe('debug.reasonRBR.hasViolations'); }); it('returns correct reason when there are transaction thread violations', async () => { - const threadReport: Report = { - reportID: '1', - type: CONST.REPORT.TYPE.CHAT, - chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - reportName: 'My first chat', - lastMessageText: 'Hello World!', - stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS_NUM.OPEN, - parentReportID: '0', - parentReportActionID: '0', + const report: Report = { + reportID: '0', + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: 1234, }; const reportActions: ReportActions = { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -1533,19 +1527,21 @@ describe('DebugUtils', () => { text: '', }, created: '2024-07-13 06:02:11.111', + childReportID: '1', }, }; await Onyx.multiSet({ [ONYXKEYS.SESSION]: { accountID: 1234, }, - [`${ONYXKEYS.COLLECTION.REPORT}0` as const]: { - reportID: '0', - type: CONST.REPORT.TYPE.EXPENSE, - ownerAccountID: 1234, + [`${ONYXKEYS.COLLECTION.REPORT}0` as const]: report, + [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: { + reportID: '1', + parentReportActionID: '0', + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATE_NUM.SUBMITTED, }, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}0` as const]: reportActions, - [`${ONYXKEYS.COLLECTION.REPORT}1` as const]: threadReport, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}0` as const]: [ { type: CONST.VIOLATION_TYPES.VIOLATION, @@ -1553,7 +1549,8 @@ describe('DebugUtils', () => { }, ], }); - const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(threadReport, reportActions, false) ?? {}; + debugger; + const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, reportActions, false) ?? {}; expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations'); }); }); From d47ed26cd6acd85e8b4847f6eb7e817599ec1acf Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 17 Oct 2024 13:05:27 +0100 Subject: [PATCH 04/12] fix(debug mode): hasRBR not showing in visible in LHN --- src/pages/Debug/Report/DebugReportPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Debug/Report/DebugReportPage.tsx b/src/pages/Debug/Report/DebugReportPage.tsx index aae4481edfae..fe26fed0c9c0 100644 --- a/src/pages/Debug/Report/DebugReportPage.tsx +++ b/src/pages/Debug/Report/DebugReportPage.tsx @@ -58,7 +58,6 @@ function DebugReportPage({ return []; } - const reasonLHN = DebugUtils.getReasonForShowingRowInLHN(report); const shouldDisplayViolations = ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction); const shouldDisplayReportViolations = ReportUtils.isReportOwner(report) && ReportUtils.hasReportViolations(reportID); const hasViolations = !!shouldDisplayViolations || shouldDisplayReportViolations; @@ -66,6 +65,7 @@ function DebugReportPage({ const {reason: reasonRBR, reportAction: reportActionRBR} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, reportActions, hasViolations) ?? {}; const hasRBR = !!reasonRBR; const hasGBR = !hasRBR && !!reasonGBR; + const reasonLHN = DebugUtils.getReasonForShowingRowInLHN(report, hasRBR); return [ { From 03579b97eb942127e89ef0bb36787eb85ce90a6f Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 17 Oct 2024 15:47:17 +0100 Subject: [PATCH 05/12] chore: fix eslint issues --- tests/unit/DebugUtilsTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/DebugUtilsTest.ts b/tests/unit/DebugUtilsTest.ts index 6e3b41281991..3fda336f3051 100644 --- a/tests/unit/DebugUtilsTest.ts +++ b/tests/unit/DebugUtilsTest.ts @@ -1549,7 +1549,6 @@ describe('DebugUtils', () => { }, ], }); - debugger; const {reason} = DebugUtils.getReasonAndReportActionForRBRInLHNRow(report, reportActions, false) ?? {}; expect(reason).toBe('debug.reasonRBR.hasTransactionThreadViolations'); }); From 1066c8d62472c4d938d47d2e1fba88dd026f0a2c Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 18 Oct 2024 09:36:17 +0100 Subject: [PATCH 06/12] chore(debug mode): update translation for has errors --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f2328cf5a602..e5040fa47e70 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -5094,7 +5094,7 @@ const translations = { hasMissingInvoiceBankAccount: 'Has missing invoice bank account', }, reasonRBR: { - hasErrors: 'Has errors', + hasErrors: 'Has errors in report or report actions data', hasViolations: 'Has violations', hasTransactionThreadViolations: 'Has transaction thread violations', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 68bc16022c34..bf392c40edbb 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5608,7 +5608,7 @@ const translations = { hasMissingInvoiceBankAccount: 'Falta la cuenta bancaria de la factura', }, reasonRBR: { - hasErrors: 'Tiene errores', + hasErrors: 'Tiene errores en los datos del informe o de las acciones del informe', hasViolations: 'Tiene violaciones', hasTransactionThreadViolations: 'Tiene violaciones de hilo de transacciones', }, From 7d137ab31f296a66a7b36ecb06e33a7debe11170 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Fri, 18 Oct 2024 09:37:55 +0100 Subject: [PATCH 07/12] fix: VirtualizedLists should never be nested inside plain ScrollViews --- src/pages/Debug/Report/DebugReportActions.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pages/Debug/Report/DebugReportActions.tsx b/src/pages/Debug/Report/DebugReportActions.tsx index e7c4059fffe7..9725653383e8 100644 --- a/src/pages/Debug/Report/DebugReportActions.tsx +++ b/src/pages/Debug/Report/DebugReportActions.tsx @@ -1,10 +1,10 @@ import React from 'react'; +import {View} from 'react-native'; import type {ListRenderItemInfo} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FlatList from '@components/FlatList'; import {PressableWithFeedback} from '@components/Pressable'; -import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -37,10 +37,7 @@ function DebugReportActions({reportID}: DebugReportActionsProps) { ); return ( - +