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
68 changes: 8 additions & 60 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ import Parser from './Parser';
import {getParsedMessageWithShortMentions} from './ParsingUtils';
import {getBankAccountLastFourDigits} from './PaymentUtils';
import Permissions from './Permissions';
import {getAccountIDsByLogins, getDisplayNameOrDefault, getLoginByAccountID, getLoginsByAccountIDs, getPersonalDetailByEmail, getShortMentionIfFound} from './PersonalDetailsUtils';
import {getAccountIDsByLogins, getDisplayNameOrDefault, getLoginByAccountID, getPersonalDetailByEmail} from './PersonalDetailsUtils';
import {
canSendInvoiceFromWorkspace,
getActivePolicies,
Expand Down Expand Up @@ -985,11 +985,6 @@ getEnvironment().then((env) => {
environment = env;
});

// This cache is used to save parse result of report action html message into text
// to prevent unnecessary parsing when the report action is not changed/modified.
// Example case: when we need to get a report name of a thread which is dependent on a report action message.
const parsedReportActionMessageCache: Record<string, string> = {};

/**
* Fallback title field used when a policy has an empty fieldList (matches OldDot behavior).
*/
Expand Down Expand Up @@ -4939,8 +4934,9 @@ function getNextApproverAccountID(report: OnyxEntry<Report>, isUnapproved = fals
return bypassApproverAccountID === deprecatedCurrentUserAccountID && !isUnapproved ? undefined : bypassApproverAccountID;
}

const approvalChain = getApprovalChain(policy, report);
const submitToAccountID = getSubmitToAccountID(policy, report, getLoginByAccountID(report?.ownerAccountID, allPersonalDetails));
const ownerLogin = getLoginByAccountID(report?.ownerAccountID, allPersonalDetails);
const approvalChain = getApprovalChain(policy, report, ownerLogin);
const submitToAccountID = getSubmitToAccountID(policy, report, ownerLogin);

if (isUnapproved) {
if (approvalChain.includes(deprecatedCurrentUserEmail ?? '')) {
Expand Down Expand Up @@ -5858,52 +5854,6 @@ function getModifiedExpenseOriginalMessage(
return originalMessage;
}

/**
* Parse html of reportAction into text
*/
function parseReportActionHtmlToText(reportAction: OnyxEntry<ReportAction>, reportID: string | undefined, conciergeReportID: string | undefined, childReportID?: string): string {
if (!reportAction) {
return '';
}
const key = `${reportID}_${reportAction.reportActionID}_${reportAction.lastModified}`;
const cachedText = parsedReportActionMessageCache[key];
if (cachedText !== undefined) {
return cachedText;
}

const {html, text} = getReportActionMessageReportUtils(reportAction) ?? {};

if (!html) {
return text ?? '';
}

const mentionReportRegex = /<mention-report reportID="?(\d+)"?(?: *\/>|><\/mention-report>)/gi;
const matches = html.matchAll(mentionReportRegex);

const reportIDToName: Record<string, string> = {};
for (const match of matches) {
if (match[1] !== childReportID) {
reportIDToName[match[1]] = getReportName(getReportOrDraftReport(match[1]), reportAttributesDerivedValue) ?? '';
}
}

const mentionUserRegex = /(?:<mention-user accountID="?(\d+)"?(?: *\/>|><\/mention-user>))/gi;
const accountIDToName: Record<string, string> = {};
const accountIDs = Array.from(html.matchAll(mentionUserRegex), (mention) => Number(mention[1]));
const logins = getLoginsByAccountIDs(accountIDs);
for (const [index, id] of accountIDs.entries()) {
const login = logins.at(index);
const user = allPersonalDetails?.[id];
const displayName = formatPhoneNumberPhoneUtils(login ?? '') || getDisplayNameOrDefault(user);
accountIDToName[id] = getShortMentionIfFound(displayName, id.toString(), currentUserPersonalDetails, login) ?? '';
}

const textMessage = Str.removeSMSDomain(Parser.htmlToText(html, {reportIDToName, accountIDToName}));
parsedReportActionMessageCache[key] = textMessage;

return textMessage;
}

/**
* Get the payee name given a report.
*/
Expand Down Expand Up @@ -12517,11 +12467,10 @@ function getBypassApproverAccountIDIfTakenControl(expenseReport: OnyxEntry<Repor
return null;
}

function getApprovalChain(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>): string[] {
function getApprovalChain(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>, ownerLogin: string | undefined): string[] {
const approvalChain: string[] = [];
const fullApprovalChain: string[] = [];
const reportTotal = expenseReport?.total ?? 0;
const submitterEmail = getLoginsByAccountIDs([expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID]).at(0) ?? '';

if (isSubmitAndClose(policy)) {
return approvalChain;
Expand All @@ -12533,13 +12482,13 @@ function getApprovalChain(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Re
// Push rule approvers to approvalChain list before submitsTo/forwardsTo approvers
for (const ruleApprover of ruleApprovers) {
// Don't push submitter to approve as a rule approver
if (fullApprovalChain.includes(ruleApprover) || ruleApprover === submitterEmail) {
if (fullApprovalChain.includes(ruleApprover) || ruleApprover === ownerLogin) {
continue;
}
fullApprovalChain.push(ruleApprover);
}

let nextApproverEmail = getManagerAccountEmail(policy, submitterEmail);
let nextApproverEmail = getManagerAccountEmail(policy, ownerLogin);

while (nextApproverEmail && !approvalChain.includes(nextApproverEmail)) {
approvalChain.push(nextApproverEmail);
Expand All @@ -12554,7 +12503,7 @@ function getApprovalChain(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Re
fullApprovalChain.push(approver);
}

if (fullApprovalChain.at(-1) === submitterEmail && policy?.preventSelfApproval) {
if (fullApprovalChain.at(-1) === ownerLogin && policy?.preventSelfApproval) {
fullApprovalChain.pop();
}
return fullApprovalChain;
Expand Down Expand Up @@ -13509,7 +13458,6 @@ export {
navigateToPrivateNotes,
navigateBackOnDeleteTransaction,
parseReportRouteParams,
parseReportActionHtmlToText,
requiresAttentionFromCurrentUser,
selectFilteredReportActions,
shouldAutoFocusOnKeyPress,
Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/IOU/ReportWorkflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,7 @@ function submitReport({
const isSubmitAndClosePolicy = isSubmitAndClose(policy);
const adminAccountID = policy?.role === CONST.POLICY.ROLE.ADMIN ? currentUserAccountIDParam : undefined;
const parentReport = getReportOrDraftReport(expenseReport.parentReportID);
const approvalChain = getApprovalChain(policy, expenseReport);
const approvalChain = getApprovalChain(policy, expenseReport, submitterLogin);
const managerIDFromChain = getKnownAccountIDByLogin(approvalChain.at(0));
const trimmedManagerEmail = managerEmail?.trim();
const managerAccountIDFromEmail = trimmedManagerEmail ? getAccountIDForSubmitManagerEmail(trimmedManagerEmail, policy?.employeeList) : undefined;
Expand Down
6 changes: 3 additions & 3 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1623,8 +1623,8 @@ function openReport(params: OpenReportActionParams) {
const parentReport =
transactionParentReportID === optimisticSelfDMReport?.reportID ? optimisticSelfDMReport : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionParentReportID}`];
const submitterAccountID = parentReport?.ownerAccountID ?? currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID;
const submitterEmail = PersonalDetailsUtils.getLoginsByAccountIDs([submitterAccountID]).at(0) ?? currentUserLogin ?? '';
const submitterPersonalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(submitterEmail);
const submitterPersonalDetails = submitterAccountID ? personalDetails?.[submitterAccountID] : undefined;
const submitterEmail = submitterPersonalDetails?.login ?? currentUserLogin ?? '';

const optimisticIOUAction = buildOptimisticIOUReportAction({
type: isSelfDM(parentReport) ? CONST.IOU.REPORT_ACTION_TYPE.TRACK : CONST.IOU.REPORT_ACTION_TYPE.CREATE,
Expand Down Expand Up @@ -3504,7 +3504,7 @@ function toggleSubscribeToChildReport(
currentUserAccountID,
});

const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs);
const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs, personalDetails);
const participants = buildParticipantInfoFromLogins(participantLogins);
openReport({
reportID: newChat.reportID,
Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1032,7 +1032,7 @@ function submitMoneyRequestOnSearch(
];

const trimmedManagerEmail = managerEmail?.trim();
const managerIDFromChain = getKnownAccountIDByLogin(getApprovalChain(firstPolicy, firstReport).at(0));
const managerIDFromChain = getKnownAccountIDByLogin(getApprovalChain(firstPolicy, firstReport, submitterLogin).at(0));
const managerAccountIDFromEmail = trimmedManagerEmail ? getAccountIDForSubmitManagerEmail(trimmedManagerEmail, firstPolicy?.employeeList) : undefined;
const submitReportManagerAccountID = getSubmitReportManagerAccountID(firstPolicy, firstReport, submitterLogin);
const resolvedManagerAccountID = trimmedManagerEmail ? (managerAccountID ?? managerAccountIDFromEmail ?? managerIDFromChain ?? firstReport.managerID) : submitReportManagerAccountID;
Expand Down
45 changes: 5 additions & 40 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ import {
isSortableColumnName,
isUnread,
isWorkspaceMemberLeavingWorkspaceRoom,
parseReportActionHtmlToText,
parseReportRouteParams,
prepareOnboardingOnyxData,
pushTransactionAutoSelectionsOnyxData,
Expand Down Expand Up @@ -3291,40 +3290,6 @@ describe('ReportUtils', () => {
});
});

describe('parseReportActionHtmlToText', () => {
it('should return empty string for undefined reportAction', () => {
const result = parseReportActionHtmlToText(undefined, '123', undefined);
expect(result).toBe('');
});

it('should return text from reportAction message when no html', () => {
const reportAction = createMock<ReportAction>({
...createRandomReportAction(1),
message: [{type: 'COMMENT', text: 'Hello world'}],
});
const result = parseReportActionHtmlToText(reportAction, '123', undefined);
expect(result).toBe('Hello world');
});

it('should parse html to text with conciergeReportID', () => {
const reportAction = createMock<ReportAction>({
...createRandomReportAction(1),
message: [{type: 'COMMENT', html: '<p>Hello world</p>', text: 'Hello world'}],
});
const result = parseReportActionHtmlToText(reportAction, '123', '999');
expect(result).toBe('Hello world');
});

it('should handle conciergeReportID being undefined', () => {
const reportAction = createMock<ReportAction>({
...createRandomReportAction(1),
message: [{type: 'COMMENT', html: '<p>Test message</p>', text: 'Test message'}],
});
const result = parseReportActionHtmlToText(reportAction, '456', undefined);
expect(result).toBe('Test message');
});
});

describe('requiresAttentionFromCurrentUser', () => {
afterEach(async () => {
await Onyx.clear();
Expand Down Expand Up @@ -8019,7 +7984,7 @@ describe('ReportUtils', () => {
type: CONST.REPORT.TYPE.EXPENSE,
};

expect(getApprovalChain(policyTest, expenseReport)).toStrictEqual([]);
expect(getApprovalChain(policyTest, expenseReport, undefined)).toStrictEqual([]);
});
});
describe('basic/advance workflow', () => {
Expand All @@ -8040,7 +8005,7 @@ describe('ReportUtils', () => {
};
Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails).then(() => {
const result = ['owner@test.com'];
expect(getApprovalChain(policyTest, expenseReport)).toStrictEqual(result);
expect(getApprovalChain(policyTest, expenseReport, undefined)).toStrictEqual(result);
});
});
it('should return list contain submitsTo of ownerAccountID and the forwardsTo of them if the policy use advance workflow', () => {
Expand All @@ -8059,7 +8024,7 @@ describe('ReportUtils', () => {
};
Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails).then(() => {
const result = ['admin@test.com'];
expect(getApprovalChain(policyTest, expenseReport)).toStrictEqual(result);
expect(getApprovalChain(policyTest, expenseReport, personalDetails[employeeAccountID]?.login)).toStrictEqual(result);
});
});
});
Expand Down Expand Up @@ -8104,7 +8069,7 @@ describe('ReportUtils', () => {
},
}).then(() => {
const result = ['owner@test.com'];
expect(getApprovalChain(policyTest, expenseReport)).toStrictEqual(result);
expect(getApprovalChain(policyTest, expenseReport, personalDetails[employeeAccountID]?.login)).toStrictEqual(result);
});
});
});
Expand Down Expand Up @@ -8164,7 +8129,7 @@ describe('ReportUtils', () => {
transactions_4: transaction4,
}).then(() => {
const result = [categoryApprover2Email, categoryApprover1Email, tagApprover2Email, tagApprover1Email, 'admin@test.com'];
expect(getApprovalChain(policyTest, expenseReport)).toStrictEqual(result);
expect(getApprovalChain(policyTest, expenseReport, personalDetails[employeeAccountID]?.login)).toStrictEqual(result);
});
});
});
Expand Down
Loading