-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Optimize getSubmitToAccountID #83901
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
Changes from 13 commits
0df5636
dcc164f
1049693
895f137
65113c6
c02d6ee
55c0b2e
7f3d719
f9fded9
3abb360
1b44f73
3ad0fa6
c6a548b
24216d3
bf5df52
3192047
c31fe24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,7 +52,7 @@ | |
| import {isOffline as isOfflineNetworkStore} from './Network/NetworkStore'; | ||
| import {formatMemberForList} from './OptionsListUtils'; | ||
| import type {MemberForList} from './OptionsListUtils'; | ||
| import {getAccountIDsByLogins, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; | ||
| import {getAccountIDsByLogins, getLoginByAccountID, getLoginsByAccountIDs, getPersonalDetailByEmail} from './PersonalDetailsUtils'; | ||
| import {getAllSortedTransactions, getCategory, getTag, getTagArrayFromName} from './TransactionUtils'; | ||
| import {isPublicDomain} from './ValidationUtils'; | ||
|
|
||
|
|
@@ -72,7 +72,7 @@ | |
|
|
||
| let allPolicies: OnyxCollection<Policy>; | ||
|
|
||
| Onyx.connect({ | ||
| key: ONYXKEYS.COLLECTION.POLICY, | ||
| waitForCollectionCallback: true, | ||
| callback: (value) => (allPolicies = value), | ||
|
|
@@ -1013,34 +1013,90 @@ | |
|
|
||
| function getManagerAccountID(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report> | {ownerAccountID: number}) { | ||
| const employeeAccountID = expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| const employeeLogin = getLoginsByAccountIDs([employeeAccountID]).at(0) ?? ''; | ||
| const employeeLogin = getLoginByAccountID(employeeAccountID) ?? ''; | ||
| const defaultApprover = getDefaultApprover(policy); | ||
|
|
||
| // For policy using the optional or basic workflow, the manager is the policy default approver. | ||
| if (([CONST.POLICY.APPROVAL_MODE.OPTIONAL, CONST.POLICY.APPROVAL_MODE.BASIC] as Array<ValueOf<typeof CONST.POLICY.APPROVAL_MODE>>).includes(getApprovalWorkflow(policy))) { | ||
| return getAccountIDsByLogins([defaultApprover]).at(0) ?? -1; | ||
| return getPersonalDetailByEmail(defaultApprover)?.accountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| } | ||
|
|
||
| const employee = policy?.employeeList?.[employeeLogin]; | ||
| if (!employee && !defaultApprover) { | ||
| return -1; | ||
| return CONST.DEFAULT_NUMBER_ID; | ||
| } | ||
|
|
||
| return getAccountIDsByLogins([employee?.submitsTo ?? defaultApprover]).at(0) ?? -1; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same goes for this function. Earlier we were returning -1 as default so we should make sure that everywhere we were using this function does not use |
||
| return getPersonalDetailByEmail(employee?.submitsTo ?? defaultApprover)?.accountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the accountID to whom the given expenseReport submits reports to in the given Policy. | ||
| */ | ||
| function getSubmitToAccountID(policy: OnyxEntry<Policy>, expenseReport: OnyxEntry<Report>): number { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see that we use this function's return value to compare with the ownerAccountID at many places. If we return 0 as the default, that condition will become true for optimistic reports, as it defaults to 0 for ownerAccountIDs. Maybe the reason to use |
||
| const ruleApprovers = getRuleApprovers(policy, expenseReport); | ||
| const employeeAccountID = expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| const employeeLogin = getLoginsByAccountIDs([employeeAccountID]).at(0) ?? ''; | ||
| if (ruleApprovers.length > 0 && ruleApprovers.at(0) === employeeLogin) { | ||
| ruleApprovers.shift(); | ||
| } | ||
| if (ruleApprovers.length > 0 && !isSubmitAndClose(policy)) { | ||
| return getAccountIDsByLogins([ruleApprovers.at(0) ?? '']).at(0) ?? -1; | ||
| const approvalRules = policy?.rules?.approvalRules; | ||
|
|
||
| // Skip rule evaluation entirely for "Submit & Close" policies and policies with no approval rules. | ||
| if (!isSubmitAndClose(policy) && approvalRules?.length) { | ||
| // Pre-build a lookup map of { category: { value β approver }, tag: { value β approver } } | ||
| // from the policy's approval rules so that each transaction's category/tag can be resolved in O(1). | ||
| const rulesMap: Record<'category' | 'tag', Record<string, string>> = {category: {}, tag: {}}; | ||
|
|
||
| for (let i = 0; i < approvalRules.length; i++) { | ||
| const rule = approvalRules.at(i); | ||
| if (!rule) { | ||
| continue; | ||
| } | ||
| for (let j = 0; j < rule.applyWhen.length; j++) { | ||
| const applyWhen = rule.applyWhen.at(j); | ||
| if (!applyWhen || applyWhen.condition !== CONST.POLICY.RULE_CONDITIONS.MATCHES) { | ||
| continue; | ||
| } | ||
| if (applyWhen.field === CONST.POLICY.FIELDS.CATEGORY || applyWhen.field === CONST.POLICY.FIELDS.TAG) { | ||
| rulesMap[applyWhen.field][applyWhen.value] = rule.approver; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!isEmptyObject(rulesMap.category) || !isEmptyObject(rulesMap.tag)) { | ||
| const allReportTransactions = getAllSortedTransactions(expenseReport?.reportID); | ||
|
|
||
| // Skip rule evaluation if the report has no transactions β there is nothing to match rules against. | ||
| if (allReportTransactions.length) { | ||
| const employeeAccountID = expenseReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| const employeeLogin = getLoginByAccountID(employeeAccountID); | ||
|
|
||
| let firstCategoryApprover = ''; | ||
| let firstTagApprover = ''; | ||
|
|
||
| for (let i = 0; i < allReportTransactions.length; i++) { | ||
| const transaction = allReportTransactions.at(i); | ||
| const category = getCategory(transaction); | ||
| const categoryApprover = rulesMap.category[category]; | ||
|
|
||
| // Category approvers take strict priority over tag approvers. | ||
| // Break immediately on the first match so we don't keep scanning transactions unnecessarily. | ||
| if (categoryApprover && categoryApprover !== employeeLogin) { | ||
| firstCategoryApprover = categoryApprover; | ||
| break; | ||
| } | ||
|
|
||
| // Only look for a tag approver if we haven't found one yet β no need to re-check on subsequent transactions. | ||
| if (!firstTagApprover) { | ||
| const tag = getTag(transaction); | ||
| const tagApprover = rulesMap.tag[tag]; | ||
|
|
||
| if (tagApprover && tagApprover !== employeeLogin) { | ||
| firstTagApprover = tagApprover; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const ruleApprover = firstCategoryApprover || firstTagApprover; | ||
| if (ruleApprover) { | ||
| return getPersonalDetailByEmail(ruleApprover)?.accountID ?? CONST.DEFAULT_NUMBER_ID; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return getManagerAccountID(policy, expenseReport); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replacing
getAccountIDsByLogins()withgetPersonalDetailByEmail()here removes the optimistic-account-ID fallback when an approver login is not yet present inPERSONAL_DETAILS_LIST. In that case this now returns0, and callers like the submit flow (managerAccountID: getSubmitToAccountID(...)) can send/keep an invalid manager instead of the previous deterministic generated account ID. This regression is triggered whenever policy approver emails are known but their personal details have not been hydrated locally yet.Useful? React with πΒ / π.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bernhardoj What do you say about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both optimistic or DEFAULT_NUMBER account ID are wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we treat
CONST.DEFAULT_NUMBER_IDdifferently in the app. Code might be using this to prevent a few things or to short-circuit things. While a randomly generated ID will still bypass those checks and render the data with that temp Id until we have real one.