Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug Mode - "Has RBR" is not shown for report that has RBR in LHN #50468

Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4931,6 +4931,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 @@ -5443,6 +5443,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
11 changes: 7 additions & 4 deletions src/libs/DebugUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,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 @@ -598,7 +597,7 @@ function getReasonForShowingRowInLHN(report: OnyxEntry<Report>): TranslationPath
return null;
}

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

const reason = ReportUtils.reasonForReportToBeInOptionList({
report,
Expand All @@ -612,8 +611,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
// 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) {
if (ReportUtils.hasReportErrorsOtherThanFailedReceipt(report, doesReportHaveViolations, transactionViolations)) {
pac-guerreiro marked this conversation as resolved.
Show resolved Hide resolved
return `debug.reasonVisibleInLHN.hasRBR`;
}
return 'debug.reasonVisibleInLHN.isFocused';
}

Expand Down Expand Up @@ -646,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 @@ -342,26 +341,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 @@ -480,78 +459,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 @@ -745,7 +652,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 @@ -813,7 +720,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 @@ -1767,23 +1674,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 @@ -1894,7 +1784,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 @@ -2625,7 +2515,6 @@ export {
getPersonalDetailsForAccountIDs,
getIOUConfirmationOptionsFromPayeePersonalDetail,
isSearchStringMatchUserDetails,
getAllReportErrors,
getPolicyExpenseReportOption,
getIOUReportIDOfLastAction,
getParticipantsOption,
Expand All @@ -2651,13 +2540,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 getIsSmallScreenWidth from './getIsSmallScreenWidth';
import isReportMessageAttachment from './isReportMessageAttachment';
Expand Down Expand Up @@ -1375,7 +1376,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 @@ -6217,6 +6218,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)) {
Copy link
Contributor

@allgandalf allgandalf Nov 25, 2024

Choose a reason for hiding this comment

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

We shouldn't had shown a RBR for archieved workspaces, this caused #50573

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@allgandalf I just moved this function from OptionsListUtils to ReportUtils in this PR.

If there was a problem, it already existed before this PR.

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 @@ -8428,6 +8535,10 @@ export {
hasMissingInvoiceBankAccount,
reasonForReportToBeInOptionList,
getReasonAndReportActionThatRequiresAttention,
hasReportErrorsOtherThanFailedReceipt,
shouldShowViolations,
getAllReportErrors,
getAllReportActionsErrorsAndReportActionThatRequiresAttention,
};

export type {
Expand Down
Loading
Loading