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

Drop domain names when searching for users and auto-filling mentions #36697

Merged
merged 10 commits into from
Mar 1, 2024
Merged
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,8 @@ const CONST = {
PATH_WITHOUT_POLICY_ID: /\/w\/[a-zA-Z0-9]+(\/|$)/,

POLICY_ID_FROM_PATH: /\/w\/([a-zA-Z0-9]+)(\/|$)/,

SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#]+(?:\\.[\\w\\-\\'\\+]+)*`, 'gim'),
},

PRONOUNS: {
Expand Down
18 changes: 3 additions & 15 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,17 +261,6 @@ Onyx.connect({
},
});

/**
* Adds expensify SMS domain (@expensify.sms) if login is a phone number and if it's not included yet
*/
function addSMSDomainIfPhoneNumber(login: string): string {
const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(login);
if (parsedPhoneNumber.possible && !Str.isValidEmail(login)) {
return `${parsedPhoneNumber.number?.e164}${CONST.SMS.DOMAIN}`;
}
return login;
}

/**
* @param defaultValues {login: accountID} In workspace invite page, when new user is added we pass available data to opt in
* @returns Returns avatar data for a list of user accountIDs
Expand Down Expand Up @@ -780,7 +769,7 @@ function isCurrentUser(userDetails: PersonalDetails): boolean {
}

// If user login is a mobile number, append sms domain if not appended already.
const userDetailsLogin = addSMSDomainIfPhoneNumber(userDetails.login ?? '');
const userDetailsLogin = PhoneNumber.addSMSDomainIfPhoneNumber(userDetails.login ?? '');

if (currentUserLogin?.toLowerCase() === userDetailsLogin.toLowerCase()) {
return true;
Expand Down Expand Up @@ -1620,7 +1609,7 @@ function getOptions(
const noOptions = recentReportOptions.length + personalDetailsOptions.length === 0 && !currentUserOption;
const noOptionsMatchExactly = !personalDetailsOptions
.concat(recentReportOptions)
.find((option) => option.login === addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase() || option.login === searchValue?.toLowerCase());
.find((option) => option.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase() || option.login === searchValue?.toLowerCase());

if (
searchValue &&
Expand All @@ -1629,7 +1618,7 @@ function getOptions(
selectedOptions.every((option) => 'login' in option && option.login !== searchValue) &&
((Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue) && !Str.endsWith(searchValue, CONST.SMS.DOMAIN)) ||
(parsedPhoneNumber.possible && Str.isValidPhone(LoginUtils.getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? '')))) &&
!optionsToExclude.find((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) &&
!optionsToExclude.find((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) &&
(searchValue !== CONST.EMAIL.CHRONOS || Permissions.canUseChronos(betas)) &&
!excludeUnknownUsers
) {
Expand Down Expand Up @@ -2010,7 +1999,6 @@ function formatSectionsFromSearchTerm(
}

export {
addSMSDomainIfPhoneNumber,
getAvatarsForAccountIDs,
isCurrentUser,
isPersonalDetailsReady,
Expand Down
14 changes: 13 additions & 1 deletion src/libs/PhoneNumber.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// eslint-disable-next-line no-restricted-imports
import {parsePhoneNumber as originalParsePhoneNumber} from 'awesome-phonenumber';
import type {ParsedPhoneNumber, ParsedPhoneNumberInvalid, PhoneNumberParseOptions} from 'awesome-phonenumber';
import Str from 'expensify-common/lib/str';
import CONST from '@src/CONST';

/**
Expand Down Expand Up @@ -39,5 +40,16 @@ function parsePhoneNumber(phoneNumber: string, options?: PhoneNumberParseOptions
} as ParsedPhoneNumberInvalid;
}

/**
* Adds expensify SMS domain (@expensify.sms) if login is a phone number and if it's not included yet
*/
function addSMSDomainIfPhoneNumber(login: string): string {
const parsedPhoneNumber = parsePhoneNumber(login);
if (parsedPhoneNumber.possible && !Str.isValidEmail(login)) {
return `${parsedPhoneNumber.number?.e164}${CONST.SMS.DOMAIN}`;
}
return login;
}

// eslint-disable-next-line import/prefer-default-export
export {parsePhoneNumber};
export {parsePhoneNumber, addSMSDomainIfPhoneNumber};
27 changes: 26 additions & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ import isReportMessageAttachment from './isReportMessageAttachment';
import localeCompare from './LocaleCompare';
import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
import {isEmailPublicDomain} from './LoginUtils';
import linkingConfig from './Navigation/linkingConfig';
import Navigation from './Navigation/Navigation';
import * as NumberUtils from './NumberUtils';
import Permissions from './Permissions';
import * as PersonalDetailsUtils from './PersonalDetailsUtils';
import * as PhoneNumber from './PhoneNumber';
import * as PolicyUtils from './PolicyUtils';
import type {LastVisibleMessage} from './ReportActionsUtils';
import * as ReportActionsUtils from './ReportActionsUtils';
Expand Down Expand Up @@ -420,6 +422,7 @@ type AncestorIDs = {
};

let currentUserEmail: string | undefined;
let currentUserPrivateDomain: string | undefined;
let currentUserAccountID: number | undefined;
let isAnonymousUser = false;

Expand All @@ -436,16 +439,19 @@ Onyx.connect({
currentUserEmail = value.email;
currentUserAccountID = value.accountID;
isAnonymousUser = value.authTokenType === 'anonymousAccount';
currentUserPrivateDomain = isEmailPublicDomain(currentUserEmail ?? '') ? '' : Str.extractEmailDomain(currentUserEmail ?? '');
},
});

let allPersonalDetails: OnyxCollection<PersonalDetails>;
let allPersonalDetailLogins: string[];
let currentUserPersonalDetails: OnyxEntry<PersonalDetails>;
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
currentUserPersonalDetails = value?.[currentUserAccountID ?? -1] ?? null;
allPersonalDetails = value ?? {};
allPersonalDetailLogins = Object.values(allPersonalDetails).map((personalDetail) => personalDetail?.login ?? '');
},
});

Expand Down Expand Up @@ -2655,7 +2661,26 @@ function hasReportNameError(report: OnyxEntry<Report>): boolean {
*/
function getParsedComment(text: string): string {
const parser = new ExpensiMark();
return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(text, {shouldEscapeText: !shouldAllowRawHTMLMessages()}) : lodashEscape(text);
const textWithMention = text.replace(CONST.REGEX.SHORT_MENTION, (match) => {
const mention = match.substring(1);

if (!Str.isValidEmail(mention) && currentUserPrivateDomain) {
const mentionWithEmailDomain = `${mention}@${currentUserPrivateDomain}`;
if (allPersonalDetailLogins.includes(mentionWithEmailDomain)) {
return `@${mentionWithEmailDomain}`;
}
}
if (Str.isValidPhone(mention)) {
const mentionWithSmsDomain = PhoneNumber.addSMSDomainIfPhoneNumber(mention);
if (allPersonalDetailLogins.includes(mentionWithSmsDomain)) {
return `@${mentionWithSmsDomain}`;
}
}

return match;
});

return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(textWithMention, {shouldEscapeText: !shouldAllowRawHTMLMessages()}) : lodashEscape(text);
}

function getReportDescriptionText(report: Report): string {
Expand Down
16 changes: 8 additions & 8 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ import * as Localize from '@libs/Localize';
import Navigation from '@libs/Navigation/Navigation';
import * as NextStepUtils from '@libs/NextStepUtils';
import * as NumberUtils from '@libs/NumberUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import Permissions from '@libs/Permissions';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
Expand Down Expand Up @@ -801,7 +801,7 @@ function getMoneyRequestInformation(
payeeEmail = currentUserEmail,
moneyRequestReportID = '',
): MoneyRequestInformation {
const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login ?? '');
const payerEmail = PhoneNumber.addSMSDomainIfPhoneNumber(participant.login ?? '');
const payerAccountID = Number(participant.accountID);
const isPolicyExpenseChat = participant.isPolicyExpenseChat;

Expand Down Expand Up @@ -1646,7 +1646,7 @@ function createSplitsAndOnyxData(
existingSplitChatReportID = '',
billable = false,
): SplitsAndOnyxData {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
const existingSplitChatReport =
existingSplitChatReportID || participants[0].reportID
Expand Down Expand Up @@ -1814,7 +1814,7 @@ function createSplitsAndOnyxData(

// In case the participant is a workspace, email & accountID should remain undefined and won't be used in the rest of this code
// participant.login is undefined when the request is initiated from a group DM with an unknown user, so we need to add a default
const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
const email = isOwnPolicyExpenseChat || isPolicyExpenseChat ? '' : PhoneNumber.addSMSDomainIfPhoneNumber(participant.login ?? '').toLowerCase();
const accountID = isOwnPolicyExpenseChat || isPolicyExpenseChat ? 0 : Number(participant.accountID);
if (email === currentUserEmailForIOUSplit) {
return;
Expand Down Expand Up @@ -2110,7 +2110,7 @@ function startSplitBill(
existingSplitChatReportID = '',
billable = false,
) {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(currentUserLogin);
const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(currentUserLogin);
const participantAccountIDs = participants.map((participant) => Number(participant.accountID));
const existingSplitChatReport =
existingSplitChatReportID || participants[0].reportID
Expand Down Expand Up @@ -2274,7 +2274,7 @@ function startSplitBill(
participants.forEach((participant) => {
// Disabling this line since participant.login can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const email = participant.isOwnPolicyExpenseChat ? '' : OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login || participant.text || '').toLowerCase();
const email = participant.isOwnPolicyExpenseChat ? '' : PhoneNumber.addSMSDomainIfPhoneNumber(participant.login || participant.text || '').toLowerCase();
const accountID = participant.isOwnPolicyExpenseChat ? 0 : Number(participant.accountID);
if (email === currentUserEmailForIOUSplit) {
return;
Expand Down Expand Up @@ -2383,7 +2383,7 @@ function startSplitBill(
* @param sessionEmail - email of the current user
*/
function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportAction, updatedTransaction: OnyxTypes.Transaction, sessionAccountID: number, sessionEmail: string) {
const currentUserEmailForIOUSplit = OptionsListUtils.addSMSDomainIfPhoneNumber(sessionEmail);
const currentUserEmailForIOUSplit = PhoneNumber.addSMSDomainIfPhoneNumber(sessionEmail);
const {transactionID} = updatedTransaction;
const unmodifiedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`];

Expand Down Expand Up @@ -3218,7 +3218,7 @@ function getSendMoneyParams(
managerID: number,
recipient: Participant,
): SendMoneyParamsData {
const recipientEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(recipient.login ?? '');
const recipientEmail = PhoneNumber.addSMSDomainIfPhoneNumber(recipient.login ?? '');
const recipientAccountID = Number(recipient.accountID);
const newIOUReportDetails = JSON.stringify({
amount,
Expand Down
6 changes: 3 additions & 3 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import Log from '@libs/Log';
import * as NumberUtils from '@libs/NumberUtils';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
Expand Down Expand Up @@ -650,7 +650,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I
Object.keys(invitedEmailsToAccountIDs).forEach((email) => {
const accountID = invitedEmailsToAccountIDs[email];
const cleanAccountID = Number(accountID);
const login = OptionsListUtils.addSMSDomainIfPhoneNumber(email);
const login = PhoneNumber.addSMSDomainIfPhoneNumber(email);

const oldChat = ReportUtils.getChatByParticipantsAndPolicy([sessionAccountID, cleanAccountID], policyID);

Expand Down Expand Up @@ -731,7 +731,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I
*/
function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccountIDs, welcomeNote: string, policyID: string) {
const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const;
const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => OptionsListUtils.addSMSDomainIfPhoneNumber(memberLogin));
const logins = Object.keys(invitedEmailsToAccountIDs).map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin));
const accountIDs = Object.values(invitedEmailsToAccountIDs);

const newPersonalDetailsOnyxData = PersonalDetailsUtils.getNewPersonalDetailsOnyxData(logins, accountIDs);
Expand Down
4 changes: 2 additions & 2 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import * as ErrorUtils from '@libs/ErrorUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import LocalNotification from '@libs/Notification/LocalNotification';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import getPolicyMemberAccountIDs from '@libs/PolicyMembersUtils';
import {extractPolicyIDFromPath} from '@libs/PolicyUtils';
import * as Pusher from '@libs/Pusher/pusher';
Expand Down Expand Up @@ -2372,7 +2372,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
(accountID): accountID is number => typeof accountID === 'number',
);

const logins = inviteeEmails.map((memberLogin) => OptionsListUtils.addSMSDomainIfPhoneNumber(memberLogin));
const logins = inviteeEmails.map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin));
const newPersonalDetailsOnyxData = PersonalDetailsUtils.getNewPersonalDetailsOnyxData(logins, inviteeAccountIDs);

const optimisticData: OnyxUpdate[] = [
Expand Down
4 changes: 2 additions & 2 deletions src/libs/actions/TeachersUnite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as API from '@libs/API';
import type {AddSchoolPrincipalParams, ReferTeachersUniteVolunteerParams} from '@libs/API/parameters';
import {WRITE_COMMANDS} from '@libs/API/types';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as ReportUtils from '@libs/ReportUtils';
import type {OptimisticCreatedReportAction} from '@libs/ReportUtils';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -69,7 +69,7 @@ function referTeachersUniteVolunteer(partnerUserID: string, firstName: string, l
*/
function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName: string, policyID: string) {
const policyName = CONST.TEACHERS_UNITE.POLICY_NAME;
const loggedInEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(sessionEmail);
const loggedInEmail = PhoneNumber.addSMSDomainIfPhoneNumber(sessionEmail);
const reportCreationData: ReportCreationData = {};

const expenseChatData = ReportUtils.buildOptimisticChatReport([sessionAccountID], '', CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, policyID, sessionAccountID, true, policyName);
Expand Down
10 changes: 6 additions & 4 deletions src/pages/RoomInvitePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {RootStackParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import {parsePhoneNumber} from '@libs/PhoneNumber';
import * as PhoneNumber from '@libs/PhoneNumber';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
Expand Down Expand Up @@ -56,7 +56,7 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa
const excludedUsers = useMemo(
() =>
[...PersonalDetailsUtils.getLoginsByAccountIDs(report?.visibleChatMemberAccountIDs ?? []), ...CONST.EXPENSIFY_EMAILS].map((participant) =>
OptionsListUtils.addSMSDomainIfPhoneNumber(participant),
PhoneNumber.addSMSDomainIfPhoneNumber(participant),
),
[report],
);
Expand Down Expand Up @@ -109,7 +109,7 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa
filterSelectedOptions = selectedOptions.filter((option) => {
const accountID = option?.accountID;
const isOptionInPersonalDetails = invitePersonalDetails.some((personalDetail) => accountID && personalDetail?.accountID === accountID);
const parsedPhoneNumber = parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchTerm)));
const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchTerm)));
const searchValue = parsedPhoneNumber.possible && parsedPhoneNumber.number ? parsedPhoneNumber.number.e164 : searchTerm.toLowerCase();
const isPartOfSearchTerm = option.text?.toLowerCase().includes(searchValue) ?? option.login?.toLowerCase().includes(searchValue);
return isPartOfSearchTerm ?? isOptionInPersonalDetails;
Expand Down Expand Up @@ -199,7 +199,9 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa
if (
!userToInvite &&
excludedUsers.includes(
parsePhoneNumber(LoginUtils.appendCountryCode(searchValue)).possible ? OptionsListUtils.addSMSDomainIfPhoneNumber(LoginUtils.appendCountryCode(searchValue)) : searchValue,
PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(searchValue)).possible
? PhoneNumber.addSMSDomainIfPhoneNumber(LoginUtils.appendCountryCode(searchValue))
: searchValue,
)
) {
return translate('messages.userIsAlreadyMember', {login: searchValue, name: reportName});
Expand Down
Loading
Loading