Skip to content

Commit

Permalink
Merge pull request Expensify#36697 from tienifr/fix/35532
Browse files Browse the repository at this point in the history
Drop domain names when searching for users and auto-filling mentions
  • Loading branch information
NikkiWines authored Mar 1, 2024
2 parents c8d92b7 + 599904a commit 0c09803
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 44 deletions.
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 @@ -260,17 +260,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 @@ -779,7 +768,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 @@ -1619,7 +1608,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 @@ -1628,7 +1617,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 @@ -2009,7 +1998,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 @@ -52,8 +52,8 @@ import getPlatform from '@libs/getPlatform';
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 @@ -2398,7 +2398,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

0 comments on commit 0c09803

Please sign in to comment.