Skip to content

Commit

Permalink
Merge pull request #50468 from callstack-internal/pac-guerreiro/fix/5…
Browse files Browse the repository at this point in the history
…0403-has-rbr-not-shown-in-visible-in-lhn-debug-section
  • Loading branch information
mountiny authored Oct 16, 2024
2 parents 2c211a0 + 8bab8b6 commit afe5258
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 147 deletions.
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5069,6 +5069,7 @@ const translations = {
reasonVisibleInLHN: {
hasDraftComment: 'Has draft comment',
hasGBR: 'Has GBR',
hasRBR: 'Has RBR',
pinnedByUser: 'Pinned by user',
hasIOUViolations: 'Has IOU violations',
hasAddWorkspaceRoomErrors: 'Has add workspace room errors',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5583,6 +5583,7 @@ const translations = {
reasonVisibleInLHN: {
hasDraftComment: 'Tiene comentario en borrador',
hasGBR: 'Tiene GBR',
hasRBR: 'Tiene RBR',
pinnedByUser: 'Fijado por el usuario',
hasIOUViolations: 'Tiene violaciones de IOU',
hasAddWorkspaceRoomErrors: 'Tiene errores al agregar sala de espacio de trabajo',
Expand Down
14 changes: 9 additions & 5 deletions src/libs/DebugUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
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';

class NumberError extends SyntaxError {
Expand Down Expand Up @@ -592,12 +591,12 @@ function validateReportActionJSON(json: string) {
/**
* Gets the reason for showing LHN row
*/
function getReasonForShowingRowInLHN(report: OnyxEntry<Report>): TranslationPaths | null {
function getReasonForShowingRowInLHN(report: OnyxEntry<Report>, hasRBR = false): TranslationPaths | null {
if (!report) {
return null;
}

const doesReportHaveViolations = OptionsListUtils.shouldShowViolations(report, transactionViolations);
const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations);

const reason = ReportUtils.reasonForReportToBeInOptionList({
report,
Expand All @@ -611,7 +610,12 @@ function getReasonForShowingRowInLHN(report: OnyxEntry<Report>): TranslationPath
includeSelfDM: true,
});

// When there's no specific reason, we default to isFocused since the report is only showing because we're viewing it
if (!([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array<typeof reason>).includes(reason) && hasRBR) {
return `debug.reasonVisibleInLHN.hasRBR`;
}

// When there's no specific reason, we default to isFocused if the report is only showing because we're viewing it
// Otherwise we return hasRBR if the report has errors other that failed receipt
if (reason === null || reason === CONST.REPORT_IN_LHN_REASONS.DEFAULT) {
return 'debug.reasonVisibleInLHN.isFocused';
}
Expand Down Expand Up @@ -645,7 +649,7 @@ function getReasonAndReportActionForGBRInLHNRow(report: OnyxEntry<Report>): GBRR
* Gets the report action that is causing the RBR to show up in LHN
*/
function getRBRReportAction(report: OnyxEntry<Report>, reportActions: OnyxEntry<ReportActions>): OnyxEntry<ReportAction> {
const {reportAction} = OptionsListUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions);
const {reportAction} = ReportUtils.getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions);

return reportAction;
}
Expand Down
119 changes: 3 additions & 116 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import type DeepValueOf from '@src/types/utils/DeepValueOf';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import times from '@src/utils/times';
import Timing from './actions/Timing';
import * as ErrorUtils from './ErrorUtils';
import filterArrayByMatch from './filterArrayByMatch';
import localeCompare from './LocaleCompare';
import * as LocalePhoneNumber from './LocalePhoneNumber';
Expand Down Expand Up @@ -343,26 +342,6 @@ Onyx.connect({
},
});

let allTransactions: OnyxCollection<Transaction> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (value) => {
if (!value) {
return;
}

allTransactions = Object.keys(value)
.filter((key) => !!value[key])
.reduce((result: OnyxCollection<Transaction>, key) => {
if (result) {
// eslint-disable-next-line no-param-reassign
result[key] = value[key];
}
return result;
}, {});
},
});
let activePolicyID: OnyxEntry<string>;
Onyx.connect({
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
Expand Down Expand Up @@ -481,78 +460,6 @@ function uniqFast(items: string[]): string[] {
return result;
}

type ReportErrorsAndReportActionThatRequiresAttention = {
errors: OnyxCommon.ErrorFields;
reportAction?: OnyxEntry<ReportAction>;
};

function getAllReportActionsErrorsAndReportActionThatRequiresAttention(report: OnyxEntry<Report>, reportActions: OnyxEntry<ReportActions>): ReportErrorsAndReportActionThatRequiresAttention {
const reportActionsArray = Object.values(reportActions ?? {});
const reportActionErrors: OnyxCommon.ErrorFields = {};
let reportAction: OnyxEntry<ReportAction>;

for (const action of reportActionsArray) {
if (action && !isEmptyObject(action.errors)) {
Object.assign(reportActionErrors, action.errors);

if (!reportAction) {
reportAction = action;
}
}
}
const parentReportAction: OnyxEntry<ReportAction> =
!report?.parentReportID || !report?.parentReportActionID ? undefined : allReportActions?.[report.parentReportID ?? '-1']?.[report.parentReportActionID ?? '-1'];

if (ReportActionUtils.wasActionTakenByCurrentUser(parentReportAction) && ReportActionUtils.isTransactionThread(parentReportAction)) {
const transactionID = ReportActionUtils.isMoneyRequestAction(parentReportAction) ? ReportActionUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : null;
const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !ReportUtils.isSettled(transaction?.reportID)) {
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
reportAction = undefined;
}
} else if ((ReportUtils.isIOUReport(report) || ReportUtils.isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) {
if (ReportUtils.shouldShowRBRForMissingSmartscanFields(report?.reportID ?? '-1') && !ReportUtils.isSettled(report?.reportID)) {
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
reportAction = ReportUtils.getReportActionWithMissingSmartscanFields(report?.reportID ?? '-1');
}
} else if (ReportUtils.hasSmartscanError(reportActionsArray)) {
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
reportAction = ReportUtils.getReportActionWithSmartscanError(reportActionsArray);
}

return {
errors: reportActionErrors,
reportAction,
};
}

/**
* Get an object of error messages keyed by microtime by combining all error objects related to the report.
*/
function getAllReportErrors(report: OnyxEntry<Report>, reportActions: OnyxEntry<ReportActions>): OnyxCommon.Errors {
const reportErrors = report?.errors ?? {};
const reportErrorFields = report?.errorFields ?? {};
const {errors: reportActionErrors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions);

// All error objects related to the report. Each object in the sources contains error messages keyed by microtime
const errorSources = {
reportErrors,
...reportErrorFields,
...reportActionErrors,
};

// Combine all error messages keyed by microtime into one object
const errorSourcesArray = Object.values(errorSources ?? {});
const allReportErrors = {};

for (const errors of errorSourcesArray) {
if (!isEmptyObject(errors)) {
Object.assign(allReportErrors, errors);
}
}
return allReportErrors;
}

/**
* Get the last actor display name from last actor details.
*/
Expand Down Expand Up @@ -749,7 +656,7 @@ function getLastMessageTextForReport(report: OnyxEntry<Report>, lastActorDetails
}

function hasReportErrors(report: Report, reportActions: OnyxEntry<ReportActions>) {
return !isEmptyObject(getAllReportErrors(report, reportActions));
return !isEmptyObject(ReportUtils.getAllReportErrors(report, reportActions));
}

/**
Expand Down Expand Up @@ -817,7 +724,7 @@ function createOption(
result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
result.isOwnPolicyExpenseChat = report.isOwnPolicyExpenseChat ?? false;
result.allReportErrors = getAllReportErrors(report, reportActions);
result.allReportErrors = ReportUtils.getAllReportErrors(report, reportActions);
result.brickRoadIndicator = hasReportErrors(report, reportActions) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom ?? report.pendingFields.createChat : undefined;
result.ownerAccountID = report.ownerAccountID;
Expand Down Expand Up @@ -1771,23 +1678,6 @@ function getUserToInviteOption({
return userToInvite;
}

/**
* Check whether report has violations
*/
function shouldShowViolations(report: Report, transactionViolations: OnyxCollection<TransactionViolation[]>) {
const {parentReportID, parentReportActionID} = report ?? {};
const canGetParentReport = parentReportID && parentReportActionID && allReportActions;
if (!canGetParentReport) {
return false;
}
const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {};
const parentReportAction = parentReportActions[parentReportActionID] ?? null;
if (!parentReportAction) {
return false;
}
return ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction);
}

/**
* filter options based on specific conditions
*/
Expand Down Expand Up @@ -1898,7 +1788,7 @@ function getOptions(
// Filter out all the reports that shouldn't be displayed
const filteredReportOptions = options.reports.filter((option) => {
const report = option.item;
const doesReportHaveViolations = shouldShowViolations(report, transactionViolations);
const doesReportHaveViolations = ReportUtils.shouldShowViolations(report, transactionViolations);

return ReportUtils.shouldReportBeInOptionList({
report,
Expand Down Expand Up @@ -2629,7 +2519,6 @@ export {
getPersonalDetailsForAccountIDs,
getIOUConfirmationOptionsFromPayeePersonalDetail,
isSearchStringMatchUserDetails,
getAllReportErrors,
getPolicyExpenseReportOption,
getIOUReportIDOfLastAction,
getParticipantsOption,
Expand All @@ -2655,13 +2544,11 @@ export {
getFirstKeyForList,
canCreateOptimisticPersonalDetailOption,
getUserToInviteOption,
shouldShowViolations,
getPersonalDetailSearchTerms,
getCurrentUserSearchTerms,
getEmptyOptions,
shouldUseBoldText,
getAlternateText,
getAllReportActionsErrorsAndReportActionThatRequiresAttention,
hasReportErrors,
};

Expand Down
115 changes: 113 additions & 2 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import type {Participant} from '@src/types/onyx/IOU';
import type {SelectedParticipant} from '@src/types/onyx/NewGroupChatDraft';
import type {OriginalMessageExportedToIntegration} from '@src/types/onyx/OldDotAction';
import type Onboarding from '@src/types/onyx/Onboarding';
import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {ErrorFields, Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {OriginalMessageChangeLog, PaymentMethodType} from '@src/types/onyx/OriginalMessage';
import type {Status} from '@src/types/onyx/PersonalDetails';
import type {ConnectionName} from '@src/types/onyx/Policy';
Expand All @@ -64,6 +64,7 @@ import * as SessionUtils from './actions/Session';
import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
import {hasValidDraftComment} from './DraftCommentUtils';
import * as ErrorUtils from './ErrorUtils';
import getAttachmentDetails from './fileDownload/getAttachmentDetails';
import isReportMessageAttachment from './isReportMessageAttachment';
import localeCompare from './LocaleCompare';
Expand Down Expand Up @@ -1377,7 +1378,7 @@ function findLastAccessedReport(ignoreDomainRooms: boolean, openOnAdminRoom = fa
}

// We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them.
// Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context.
// Check where findLastAccessedReport is called in MainDrawerNavigator.js for more context.
// Domain rooms are now the only type of default room that are on the defaultRooms beta.
if (ignoreDomainRooms && isDomainRoom(report) && !hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number))) {
return false;
Expand Down Expand Up @@ -6260,6 +6261,112 @@ function shouldAdminsRoomBeVisible(report: OnyxEntry<Report>): boolean {
return true;
}

/**
* Check whether report has violations
*/
function shouldShowViolations(report: Report, transactionViolations: OnyxCollection<TransactionViolation[]>) {
const {parentReportID, parentReportActionID} = report ?? {};
const canGetParentReport = parentReportID && parentReportActionID && allReportActions;
if (!canGetParentReport) {
return false;
}
const parentReportActions = allReportActions ? allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`] ?? {} : {};
const parentReportAction = parentReportActions[parentReportActionID] ?? null;
if (!parentReportAction) {
return false;
}
return shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction);
}

type ReportErrorsAndReportActionThatRequiresAttention = {
errors: ErrorFields;
reportAction?: OnyxEntry<ReportAction>;
};

function getAllReportActionsErrorsAndReportActionThatRequiresAttention(report: OnyxEntry<Report>, reportActions: OnyxEntry<ReportActions>): ReportErrorsAndReportActionThatRequiresAttention {
const reportActionsArray = Object.values(reportActions ?? {});
const reportActionErrors: ErrorFields = {};
let reportAction: OnyxEntry<ReportAction>;

for (const action of reportActionsArray) {
if (action && !isEmptyObject(action.errors)) {
Object.assign(reportActionErrors, action.errors);

if (!reportAction) {
reportAction = action;
}
}
}
const parentReportAction: OnyxEntry<ReportAction> =
!report?.parentReportID || !report?.parentReportActionID ? undefined : allReportActions?.[report.parentReportID ?? '-1']?.[report.parentReportActionID ?? '-1'];

if (ReportActionsUtils.wasActionTakenByCurrentUser(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction)) {
const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : null;
const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];
if (TransactionUtils.hasMissingSmartscanFields(transaction ?? null) && !isSettled(transaction?.reportID)) {
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
reportAction = undefined;
}
} else if ((isIOUReport(report) || isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) {
if (shouldShowRBRForMissingSmartscanFields(report?.reportID ?? '-1') && !isSettled(report?.reportID)) {
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
reportAction = getReportActionWithMissingSmartscanFields(report?.reportID ?? '-1');
}
} else if (hasSmartscanError(reportActionsArray)) {
reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericSmartscanFailureMessage');
reportAction = getReportActionWithSmartscanError(reportActionsArray);
}

return {
errors: reportActionErrors,
reportAction,
};
}

/**
* Get an object of error messages keyed by microtime by combining all error objects related to the report.
*/
function getAllReportErrors(report: OnyxEntry<Report>, reportActions: OnyxEntry<ReportActions>): Errors {
const reportErrors = report?.errors ?? {};
const reportErrorFields = report?.errorFields ?? {};
const {errors: reportActionErrors} = getAllReportActionsErrorsAndReportActionThatRequiresAttention(report, reportActions);

// All error objects related to the report. Each object in the sources contains error messages keyed by microtime
const errorSources = {
reportErrors,
...reportErrorFields,
...reportActionErrors,
};

// Combine all error messages keyed by microtime into one object
const errorSourcesArray = Object.values(errorSources ?? {});
const allReportErrors = {};

for (const errors of errorSourcesArray) {
if (!isEmptyObject(errors)) {
Object.assign(allReportErrors, errors);
}
}
return allReportErrors;
}

function hasReportErrorsOtherThanFailedReceipt(report: Report, doesReportHaveViolations: boolean, transactionViolations: OnyxCollection<TransactionViolation[]>) {
const reportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`] ?? {};
const allReportErrors = getAllReportErrors(report, reportActions) ?? {};
const transactionReportActions = ReportActionsUtils.getAllReportActions(report.reportID);
const oneTransactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, transactionReportActions, undefined);
let doesTransactionThreadReportHasViolations = false;
if (oneTransactionThreadReportID) {
const transactionReport = getReport(oneTransactionThreadReportID);
doesTransactionThreadReportHasViolations = !!transactionReport && shouldShowViolations(transactionReport, transactionViolations);
}
return (
doesTransactionThreadReportHasViolations ||
doesReportHaveViolations ||
Object.values(allReportErrors).some((error) => error?.[0] !== Localize.translateLocal('iou.error.genericSmartscanFailureMessage'))
);
}

type ShouldReportBeInOptionListParams = {
report: OnyxEntry<Report>;
currentReportId: string;
Expand Down Expand Up @@ -8473,6 +8580,10 @@ export {
reasonForReportToBeInOptionList,
getReasonAndReportActionThatRequiresAttention,
isPolicyRelatedReport,
hasReportErrorsOtherThanFailedReceipt,
shouldShowViolations,
getAllReportErrors,
getAllReportActionsErrorsAndReportActionThatRequiresAttention,
};

export type {
Expand Down
Loading

0 comments on commit afe5258

Please sign in to comment.