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
3 changes: 1 addition & 2 deletions src/libs/Fullstory/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {filterObject} from '@libs/ObjectUtils';
import {getActivePolicies, isControlPolicy} from '@libs/PolicyUtils';
import getHighestPolicyRole from '@libs/PolicyUtils/getHighestPolicyRole';
import {getActivePolicies, getHighestPolicyRole, isControlPolicy} from '@libs/PolicyUtils';

import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
Expand Down
10 changes: 10 additions & 0 deletions src/libs/PolicyUtils/approvals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';

import {isPolicyMember} from './members';
import {isPolicyApprover} from './permissions';
import {isGroupPolicy} from './policyType';
import {getTagApproverRule} from './rules';
Expand Down Expand Up @@ -90,6 +91,14 @@ function getDefaultApprover(policy: OnyxEntry<Policy>): string {
return policy?.approver || policy?.owner || '';
}

/**
* Whether an approver email points at someone who is no longer on the workspace. The owner always counts as a member,
* and HR advanced (manager) mode is excluded, because its final approver doesn't have to be a workspace member.
*/
function isNonMemberApprover(policy: OnyxEntry<Policy>, approverEmail: string | undefined): boolean {
return !!approverEmail && !isPolicyMember(policy, approverEmail) && !getHRAdvancedModeFinalApprover(policy);
}

/**
* Whether the policy has at least one custom approval workflow. A workflow is considered custom when either:
* - the default workflow was modified by changing its first approver or adding an "Approves to" user, or
Expand Down Expand Up @@ -441,6 +450,7 @@ export {
isSubmitterAndApprover,
getApprovalWorkflow,
getDefaultApprover,
isNonMemberApprover,
hasCustomApprovalWorkflow,
getRuleApprovers,
evaluateApprovalWorkflowRule,
Expand Down
113 changes: 113 additions & 0 deletions src/libs/PolicyUtils/domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Workspace domain names, admin email domains, and the workspace a card or card feed belongs to.
* Extracted from PolicyUtils/index.ts to keep that file smaller.
*/
import {isPublicDomain} from '@libs/ValidationUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Card, Policy} from '@src/types/onyx';

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';

import {Str} from 'expensify-common';

import {isExpensifyTeam} from './members';

function getDomainNameForPolicy(policyID?: string): string {
if (!policyID) {
return '';
}

return `${CONST.EXPENSIFY_POLICY_DOMAIN}${policyID.toLowerCase()}${CONST.EXPENSIFY_POLICY_DOMAIN_EXTENSION}`;
}

function getAdminsPrivateEmailDomains(policy?: Policy) {
if (!policy) {
return [];
}

const adminDomains = Object.entries(policy.employeeList ?? {}).reduce((domains, [email, employee]) => {
if (employee.role !== CONST.POLICY.ROLE.ADMIN) {
return domains;
}
domains.push(Str.extractEmailDomain(email).toLowerCase());
return domains;
}, [] as string[]);

const ownerDomains = policy.owner ? [Str.extractEmailDomain(policy.owner).toLowerCase()] : [];

const privateDomains = [...new Set(adminDomains.concat(ownerDomains))].filter((domain) => !isPublicDomain(domain));

// If the policy is not owned by Expensify there is no point in showing the domain for provisioning.
if (!isExpensifyTeam(policy.owner)) {
return privateDomains.filter((domain) => domain !== CONST.EXPENSIFY_PARTNER_NAME && domain !== CONST.EMAIL.GUIDES_DOMAIN);
}

return privateDomains;
}

/**
* Determines the most frequent domain from the `acceptedDomains` list
* that appears in the email addresses of policy members.
*
* @param acceptedDomains - List of domains to consider.
* @param policy - The policy object.
*/
function getMostFrequentEmailDomain(acceptedDomains: string[], policy?: Policy) {
if (!policy) {
return undefined;
}
const domainOccurrences = {} as Record<string, number>;
for (const memberDomain of Object.keys(policy.employeeList ?? {})
.concat(policy.owner)
.map((email) => Str.extractEmailDomain(email).toLowerCase())) {
if (!acceptedDomains.includes(memberDomain)) {
continue;
}
domainOccurrences[memberDomain] = (domainOccurrences[memberDomain] || 0) + 1;
}
let mostFrequent = {domain: '', count: 0};
for (const [domain, count] of Object.entries(domainOccurrences)) {
if (count <= mostFrequent.count) {
continue;
}
mostFrequent = {domain, count};
}
if (mostFrequent.count === 0) {
return undefined;
}
return mostFrequent.domain;
}

const getPolicyIDFromDomainName = (domainName: string): string | undefined => domainName.match(CONST.REGEX.EXPENSIFY_POLICY_DOMAIN_NAME)?.[1]?.toUpperCase();

/**
* Returns the workspace an assigned card belongs to.
*
* A card names its workspace with `fundID`, which is the policy's `policyAccountID`. That holds whatever the feed's
* domain looks like, so a card on a company's own domain resolves the same workspace as one on a workspace feed's
* `expensify-policy<ID>.exfy` domain. The domain is read only as a fallback, for a card that arrives without a `fundID`.
*/
const getPolicyForAssignedCard = (card: OnyxEntry<Pick<Card, 'domainName' | 'fundID'>>, policies: OnyxCollection<Policy>): OnyxEntry<Policy> => {
const workspaceAccountID = Number(card?.fundID);
const policyForFund = workspaceAccountID ? Object.values(policies ?? {}).find((policy) => policy?.policyAccountID === workspaceAccountID) : undefined;
if (policyForFund) {
return policyForFund;
}

const policyID = card?.domainName ? getPolicyIDFromDomainName(card.domainName) : undefined;
return policyID ? policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] : undefined;
};

const getDescriptionForPolicyDomainCard = (domainName: string, policies: OnyxCollection<Policy>): string => {
// A domain name containing a policyID indicates that this is a workspace feed
const policyID = getPolicyIDFromDomainName(domainName);
if (policyID) {
const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
return policy?.name ?? domainName;
}
return domainName;
};

export {getDomainNameForPolicy, getAdminsPrivateEmailDomains, getMostFrequentEmailDomain, getPolicyIDFromDomainName, getPolicyForAssignedCard, getDescriptionForPolicyDomainCard};
34 changes: 0 additions & 34 deletions src/libs/PolicyUtils/getHighestPolicyRole.ts

This file was deleted.

Loading
Loading