From 34fc112e5fb4aa1c6b6c53c082693f56012bd2b4 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 31 Oct 2024 13:04:46 +0000 Subject: [PATCH 01/20] #EX-4 UNMASK: Chats between Non-Expensify users and Expensify users (e.g. Concierge) MASK: Chats between Expensify users and other Expensify users Chats between non-Expensify users that do NOT include any Expensify users Exposed PersonalDetailsProvider from OnyxProvider Added CONST that represents email domain for support team: - EXPENSIFY_TEAM_EMAIL_DOMAIN Updated @lib/Fullstory to evaluate MASK/UNMASK based on provided report context Added isExpensifyAndCustomerChat exported function to ReportUtils to handle report type evaluation. --- src/CONST.ts | 1 + src/components/OnyxProvider.tsx | 3 +- src/libs/Fullstory/index.native.ts | 69 +++++++++++++++- src/libs/Fullstory/index.ts | 91 ++++++++++++++++++++- src/libs/ReportUtils.ts | 43 +++++++++- src/pages/home/report/ReportActionItem.tsx | 7 ++ src/pages/home/report/ReportActionsList.tsx | 5 +- 7 files changed, 211 insertions(+), 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 8a1e9cfbf67c..d479eb666a0e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1623,6 +1623,7 @@ const CONST = { STUDENT_AMBASSADOR: 'studentambassadors@expensify.com', SVFG: 'svfg@expensify.com', EXPENSIFY_EMAIL_DOMAIN: '@expensify.com', + EXPENSIFY_TEAM_EMAIL_DOMAIN: '@team.expensify.com', }, CONCIERGE_DISPLAY_NAME: 'Concierge', diff --git a/src/components/OnyxProvider.tsx b/src/components/OnyxProvider.tsx index 23ddf2b0c4dd..796920372c9d 100644 --- a/src/components/OnyxProvider.tsx +++ b/src/components/OnyxProvider.tsx @@ -6,7 +6,7 @@ import createOnyxContext from './createOnyxContext'; // Set up any providers for individual keys. This should only be used in cases where many components will subscribe to // the same key (e.g. FlatList renderItem components) const [withNetwork, NetworkProvider, NetworkContext] = createOnyxContext(ONYXKEYS.NETWORK); -const [, PersonalDetailsProvider, , usePersonalDetails] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS_LIST); +const [, PersonalDetailsProvider, PersonalDetailsContext, usePersonalDetails] = createOnyxContext(ONYXKEYS.PERSONAL_DETAILS_LIST); const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE); const [, BlockedFromConciergeProvider, , useBlockedFromConcierge] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [, BetasProvider, BetasContext, useBetas] = createOnyxContext(ONYXKEYS.BETAS); @@ -55,6 +55,7 @@ export { PreferredThemeContext, useBetas, useFrequentlyUsedEmojis, + PersonalDetailsContext, PreferredEmojiSkinToneContext, useBlockedFromConcierge, useSession, diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 30a5a77ae9f3..0af8258a080c 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -2,7 +2,14 @@ import FullStory, {FSPage} from '@fullstory/react-native'; import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; -import type {UserMetadata} from '@src/types/onyx'; +import type {UserMetadata, OnyxInputOrEntry, Report} from '@src/types/onyx'; +import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; + +const MASK = 'fs-mask'; +const UNMASK = 'fs-unmask'; +const CUSTOMER = "customer"; +const CONCIERGE = "concierge"; +const OTHER = "other"; /** * Fullstory React-Native lib adapter @@ -63,5 +70,63 @@ const FS = { }, }; +/** + * Placeholder function for Mobile-Web compatibility. + */ +function parseFSAttributes (): void{ + // pass +} + +/* + prefix? if component name should be used as a prefix, + in case data-test-id attribute usage, + clean component name should be preserved in data-test-id. +*/ +function getFSAttributes (name: string, mask: bool, prefix: bool): string { + // prefixed for Native apps should contain only component name + if (prefix){ + return name; + } + const componentPrefix = prefix ? `${name},`:""; + const componentSuffix = name ? `,fs-${name}`:""; + const fsAttrValue = `${componentPrefix}${mask?MASK:UNMASK}${componentSuffix}`; + /* + testID: componentName,fs-unmask,fs-componentName + fsClass: fs-unmask,fs-componentName + */ + return fsAttrValue; +} + +function getChatFSAttributes (name: string, report: OnyxInputOrEntry, prefix: bool): string { + // prefixed for Native apps should contain only component name + if (prefix){ + return name; + } + // default + let componentName = name ? `,fs-${name}`:""; + let fsAttrValue = ""; + + if(!!isConciergeChatReport(report)){ + componentName = name ? `,fs-${CONCIERGE}-${name}`:""; + /* + fs-unmask,fs-concierge-chatMessage + */ + fsAttrValue = `${UNMASK}${componentName}`; + }else if(!!isExpensifyAndCustomerChat(report)){ + componentName = name ? `,fs-${CUSTOMER}-${name}`:""; + /* + fs-mask,fs-customer-chatMessage + */ + fsAttrValue = `${MASK}${componentName}`; + } else { + componentName = name ? `,fs-${OTHER}-${name}`:""; + /* + fs-mask,fs-other-chatMessage + */ + fsAttrValue = `${MASK}${componentName}`; + } + return fsAttrValue; +} + export default FS; -export {FSPage}; +export {FSPage, parseFSAttributes, getFSAttributes, getChatFSAttributes}; diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 0aa0b2094591..0102160e0503 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -2,8 +2,16 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; import type {OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; -import type {UserMetadata} from '@src/types/onyx'; +import type {UserMetadata, OnyxInputOrEntry, Report} from '@src/types/onyx'; import type NavigationProperties from './types'; +import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; + +const WEB_PROP_ATTR="data-testid"; +const MASK = 'fs-mask'; +const UNMASK = 'fs-unmask'; +const CUSTOMER = "customer"; +const CONCIERGE = "concierge"; +const OTHER = "other"; // Placeholder Browser API does not support Manual Page definition class FSPage { @@ -16,7 +24,9 @@ class FSPage { this.properties = properties; } - start() {} + start() { + parseFSAttributes(); + } } /** @@ -92,5 +102,80 @@ const FS = { init: (_value: OnyxEntry) => {}, }; +/** + * Extract values from non-scraped at build time attribute WEB_PROP_ATTR, + * reevaluate "fs-class". + */ +function parseFSAttributes (): void{ + window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { + let attr = o.getAttribute(WEB_PROP_ATTR); + if (/fs\-/igm.test(attr)) { + let fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) || []; + o.setAttribute("fs-class", fsAttrs.join(",")); + + let cleanedAttrs = attr; + fsAttrs.forEach(fsAttr => {cleanedAttrs = cleanedAttrs.replace(fsAttr, '')}); + + cleanedAttrs = cleanedAttrs + .replace(/,+/g, ',') + .replace(/\s*,\s*/g, ',') + .replace(/^,+|,+$/g, '') + .replace(/\s+/g, ' ') + .trim(); + + cleanedAttrs + ?o.setAttribute(WEB_PROP_ATTR, cleanedAttrs) + :o.removeAttribute(WEB_PROP_ATTR); + } + }) +} + +/* + prefix? if component name should be used as a prefix, + in case data-test-id attribute usage, + clean component name should be preserved in data-test-id. +*/ +function getFSAttributes (name: string, mask: bool, prefix: bool): string { + const componentPrefix = prefix ? `${name},`:""; + const componentSuffix = name ? `,fs-${name}`:""; + const fsAttrValue = `${componentPrefix}${mask?MASK:UNMASK}${componentSuffix}`; + /* + testID: componentName,fs-unmask,fs-componentName + fsClass: fs-unmask,fs-componentName + */ + return fsAttrValue; +} + +function getChatFSAttributes (name: string, report: OnyxInputOrEntry, prefix: bool): string { + let componentPrefix = prefix ? `${name},`:""; + let componentSuffix = name ? `,fs-${name}`:""; + let fsAttrValue = ""; + + if(!!isConciergeChatReport(report)){ + componentPrefix = prefix ? `${CONCIERGE}-${name},`:""; + componentSuffix = name ? `,fs-${name}`:""; + /* + concierge-chatMessage,fs-unmask,fs-chatMessage + */ + fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; + }else if(!!isExpensifyAndCustomerChat(report)){ + componentPrefix = prefix ? `${CUSTOMER}-${name},`:""; + componentSuffix = name ? `,fs-${name}`:""; + /* + customer-chatMessage,fs-unmask,fs-chatMessage + */ + fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; + } else { + componentPrefix = prefix ? `${OTHER}-${name},`:""; + componentSuffix = name ? `,fs-${name}`:""; + /* + other-chatMessage,fs-mask,fs-chatMessage + */ + fsAttrValue = `${componentPrefix}${MASK}${componentSuffix}`; + } + + return fsAttrValue; +} + export default FS; -export {FSPage}; +export {FSPage, parseFSAttributes, getFSAttributes, getChatFSAttributes}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d8133991d62b..1e6ae4ee2d88 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -88,7 +88,8 @@ import * as TransactionUtils from './TransactionUtils'; import * as Url from './Url'; import type {AvatarSource} from './UserUtils'; import * as UserUtils from './UserUtils'; - +import {PersonalDetailsContext, PersonalDetailsProvider} from '@src/components/OnyxProvider'; +import {useContext} from 'react'; type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18; type SpendBreakdown = { @@ -8407,6 +8408,45 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry) { return isExpenseReport(report) && report?.hasParentAccess === false; } +function isExpensifyAndCustomerChat(report: OnyxInputOrEntry): boolean { + if (!report?.participants || isThread(report)) { + return false; + } + + const participantAccountIDs = new Set(Object.keys(report.participants)); + if (participantAccountIDs.size !== 2) { + return false; + } + + // by email participants + const baseRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN + "$"); + const teamRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN + "$"); + const participantsContext = useContext(PersonalDetailsContext); + + for (const participantAccountID of participantAccountIDs){ + let id = Number(participantAccountID); + let contextAccountData = participantsContext[id]; + if(!contextAccountData){ + continue + } + if(baseRegexp.test(contextAccountData.login)){ + return true + } + if(teamRegexp.test(contextAccountData.login)){ + return true + } + } + + // System users communication + const expensifyTeam = new Set(Object.values(CONST.ACCOUNT_ID)); + const expensifyTeamParticipants = expensifyTeam.intersection(participantAccountIDs) + if (expensifyTeamParticipants.size > 0){ + return true; + } + + return false +} + export { addDomainToShortMention, completeShortMention, @@ -8599,6 +8639,7 @@ export { isClosedExpenseReportWithNoExpenses, isCompletedTaskReport, isConciergeChatReport, + isExpensifyAndCustomerChat, isControlPolicyExpenseChat, isControlPolicyExpenseReport, isCurrentUserSubmitter, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a0e2f65a89a0..ea63056b1cd0 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -83,6 +83,7 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import {parseFSAttributes} from '@libs/Fullstory'; type ReportActionItemProps = { /** Report for this action */ @@ -319,6 +320,12 @@ function ReportActionItem({ setIsHidden(false); }, [latestDecision, action]); + useEffect(() => { + if (action && popoverAnchorRef.current) { + parseFSAttributes(popoverAnchorRef.current); + } + }, [action]); + const toggleContextMenuFromActiveReportAction = useCallback(() => { setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); }, [action.reportActionID]); diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 58e7fe319359..d63992ea9595 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -36,6 +36,7 @@ import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; import ReportActionsListItemRenderer from './ReportActionsListItemRenderer'; +import {parseFSAttributes, getChatFSAttributes} from '@libs/Fullstory'; type LoadNewerChats = DebouncedFunc<(params: {distanceFromStart: number}) => void>; @@ -715,7 +716,9 @@ function ReportActionsList({ isActive={(isFloatingMessageCounterVisible && !!unreadMarkerReportActionID) || canScrollToNewerComments} onClick={scrollToBottomAndMarkReportAsRead} /> - + Date: Thu, 31 Oct 2024 13:37:58 +0000 Subject: [PATCH 02/20] #EX-4 Clean perfcheck passed. --- src/libs/Fullstory/index.native.ts | 40 +++++------ src/libs/Fullstory/index.ts | 60 ++++++++-------- src/libs/ReportUtils.ts | 77 +++++++++++---------- src/pages/home/report/ReportActionItem.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 10 +-- 5 files changed, 96 insertions(+), 93 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 0af8258a080c..9f5a0cbb334d 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,15 +1,15 @@ import FullStory, {FSPage} from '@fullstory/react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; -import type {UserMetadata, OnyxInputOrEntry, Report} from '@src/types/onyx'; -import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; +import type {OnyxInputOrEntry, Report, UserMetadata} from '@src/types/onyx'; const MASK = 'fs-mask'; const UNMASK = 'fs-unmask'; -const CUSTOMER = "customer"; -const CONCIERGE = "concierge"; -const OTHER = "other"; +const CUSTOMER = 'customer'; +const CONCIERGE = 'concierge'; +const OTHER = 'other'; /** * Fullstory React-Native lib adapter @@ -73,7 +73,7 @@ const FS = { /** * Placeholder function for Mobile-Web compatibility. */ -function parseFSAttributes (): void{ +function parseFSAttributes(): void { // pass } @@ -82,14 +82,14 @@ function parseFSAttributes (): void{ in case data-test-id attribute usage, clean component name should be preserved in data-test-id. */ -function getFSAttributes (name: string, mask: bool, prefix: bool): string { +function getFSAttributes(name: string, mask: bool, prefix: bool): string { // prefixed for Native apps should contain only component name - if (prefix){ + if (prefix) { return name; } - const componentPrefix = prefix ? `${name},`:""; - const componentSuffix = name ? `,fs-${name}`:""; - const fsAttrValue = `${componentPrefix}${mask?MASK:UNMASK}${componentSuffix}`; + const componentPrefix = prefix ? `${name},` : ''; + const componentSuffix = name ? `,fs-${name}` : ''; + const fsAttrValue = `${componentPrefix}${mask ? MASK : UNMASK}${componentSuffix}`; /* testID: componentName,fs-unmask,fs-componentName fsClass: fs-unmask,fs-componentName @@ -97,29 +97,29 @@ function getFSAttributes (name: string, mask: bool, prefix: bool): string { return fsAttrValue; } -function getChatFSAttributes (name: string, report: OnyxInputOrEntry, prefix: bool): string { +function getChatFSAttributes(name: string, report: OnyxInputOrEntry, prefix: bool): string { // prefixed for Native apps should contain only component name - if (prefix){ + if (prefix) { return name; } // default - let componentName = name ? `,fs-${name}`:""; - let fsAttrValue = ""; + let componentName = name ? `,fs-${name}` : ''; + let fsAttrValue = ''; - if(!!isConciergeChatReport(report)){ - componentName = name ? `,fs-${CONCIERGE}-${name}`:""; + if (!!isConciergeChatReport(report)) { + componentName = name ? `,fs-${CONCIERGE}-${name}` : ''; /* fs-unmask,fs-concierge-chatMessage */ fsAttrValue = `${UNMASK}${componentName}`; - }else if(!!isExpensifyAndCustomerChat(report)){ - componentName = name ? `,fs-${CUSTOMER}-${name}`:""; + } else if (!!isExpensifyAndCustomerChat(report)) { + componentName = name ? `,fs-${CUSTOMER}-${name}` : ''; /* fs-mask,fs-customer-chatMessage */ fsAttrValue = `${MASK}${componentName}`; } else { - componentName = name ? `,fs-${OTHER}-${name}`:""; + componentName = name ? `,fs-${OTHER}-${name}` : ''; /* fs-mask,fs-other-chatMessage */ diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 0102160e0503..71acc1a16029 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,17 +1,17 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; import type {OnyxEntry} from 'react-native-onyx'; +import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; -import type {UserMetadata, OnyxInputOrEntry, Report} from '@src/types/onyx'; +import type {OnyxInputOrEntry, Report, UserMetadata} from '@src/types/onyx'; import type NavigationProperties from './types'; -import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; -const WEB_PROP_ATTR="data-testid"; +const WEB_PROP_ATTR = 'data-testid'; const MASK = 'fs-mask'; const UNMASK = 'fs-unmask'; -const CUSTOMER = "customer"; -const CONCIERGE = "concierge"; -const OTHER = "other"; +const CUSTOMER = 'customer'; +const CONCIERGE = 'concierge'; +const OTHER = 'other'; // Placeholder Browser API does not support Manual Page definition class FSPage { @@ -106,15 +106,17 @@ const FS = { * Extract values from non-scraped at build time attribute WEB_PROP_ATTR, * reevaluate "fs-class". */ -function parseFSAttributes (): void{ +function parseFSAttributes(): void { window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { let attr = o.getAttribute(WEB_PROP_ATTR); - if (/fs\-/igm.test(attr)) { + if (/fs\-/gim.test(attr)) { let fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) || []; - o.setAttribute("fs-class", fsAttrs.join(",")); + o.setAttribute('fs-class', fsAttrs.join(',')); let cleanedAttrs = attr; - fsAttrs.forEach(fsAttr => {cleanedAttrs = cleanedAttrs.replace(fsAttr, '')}); + fsAttrs.forEach((fsAttr) => { + cleanedAttrs = cleanedAttrs.replace(fsAttr, ''); + }); cleanedAttrs = cleanedAttrs .replace(/,+/g, ',') @@ -123,11 +125,9 @@ function parseFSAttributes (): void{ .replace(/\s+/g, ' ') .trim(); - cleanedAttrs - ?o.setAttribute(WEB_PROP_ATTR, cleanedAttrs) - :o.removeAttribute(WEB_PROP_ATTR); + cleanedAttrs ? o.setAttribute(WEB_PROP_ATTR, cleanedAttrs) : o.removeAttribute(WEB_PROP_ATTR); } - }) + }); } /* @@ -135,10 +135,10 @@ function parseFSAttributes (): void{ in case data-test-id attribute usage, clean component name should be preserved in data-test-id. */ -function getFSAttributes (name: string, mask: bool, prefix: bool): string { - const componentPrefix = prefix ? `${name},`:""; - const componentSuffix = name ? `,fs-${name}`:""; - const fsAttrValue = `${componentPrefix}${mask?MASK:UNMASK}${componentSuffix}`; +function getFSAttributes(name: string, mask: bool, prefix: bool): string { + const componentPrefix = prefix ? `${name},` : ''; + const componentSuffix = name ? `,fs-${name}` : ''; + const fsAttrValue = `${componentPrefix}${mask ? MASK : UNMASK}${componentSuffix}`; /* testID: componentName,fs-unmask,fs-componentName fsClass: fs-unmask,fs-componentName @@ -146,28 +146,28 @@ function getFSAttributes (name: string, mask: bool, prefix: bool): string { return fsAttrValue; } -function getChatFSAttributes (name: string, report: OnyxInputOrEntry, prefix: bool): string { - let componentPrefix = prefix ? `${name},`:""; - let componentSuffix = name ? `,fs-${name}`:""; - let fsAttrValue = ""; +function getChatFSAttributes(name: string, report: OnyxInputOrEntry, prefix: bool): string { + let componentPrefix = prefix ? `${name},` : ''; + let componentSuffix = name ? `,fs-${name}` : ''; + let fsAttrValue = ''; - if(!!isConciergeChatReport(report)){ - componentPrefix = prefix ? `${CONCIERGE}-${name},`:""; - componentSuffix = name ? `,fs-${name}`:""; + if (!!isConciergeChatReport(report)) { + componentPrefix = prefix ? `${CONCIERGE}-${name},` : ''; + componentSuffix = name ? `,fs-${name}` : ''; /* concierge-chatMessage,fs-unmask,fs-chatMessage */ fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; - }else if(!!isExpensifyAndCustomerChat(report)){ - componentPrefix = prefix ? `${CUSTOMER}-${name},`:""; - componentSuffix = name ? `,fs-${name}`:""; + } else if (!!isExpensifyAndCustomerChat(report)) { + componentPrefix = prefix ? `${CUSTOMER}-${name},` : ''; + componentSuffix = name ? `,fs-${name}` : ''; /* customer-chatMessage,fs-unmask,fs-chatMessage */ fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; } else { - componentPrefix = prefix ? `${OTHER}-${name},`:""; - componentSuffix = name ? `,fs-${name}`:""; + componentPrefix = prefix ? `${OTHER}-${name},` : ''; + componentSuffix = name ? `,fs-${name}` : ''; /* other-chatMessage,fs-mask,fs-chatMessage */ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1e6ae4ee2d88..2ebf7b0daad9 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -7,6 +7,7 @@ import isEmpty from 'lodash/isEmpty'; import lodashIsEqual from 'lodash/isEqual'; import isNumber from 'lodash/isNumber'; import lodashMaxBy from 'lodash/maxBy'; +import {useContext} from 'react'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {SvgProps} from 'react-native-svg'; @@ -17,6 +18,7 @@ import {FallbackAvatar, IntacctSquare, NetSuiteSquare, QBOSquare, XeroSquare} fr import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; +import {PersonalDetailsContext, PersonalDetailsProvider} from '@src/components/OnyxProvider'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; @@ -88,8 +90,7 @@ import * as TransactionUtils from './TransactionUtils'; import * as Url from './Url'; import type {AvatarSource} from './UserUtils'; import * as UserUtils from './UserUtils'; -import {PersonalDetailsContext, PersonalDetailsProvider} from '@src/components/OnyxProvider'; -import {useContext} from 'react'; + type AvatarRange = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18; type SpendBreakdown = { @@ -8409,42 +8410,42 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry) { } function isExpensifyAndCustomerChat(report: OnyxInputOrEntry): boolean { - if (!report?.participants || isThread(report)) { - return false; - } - - const participantAccountIDs = new Set(Object.keys(report.participants)); - if (participantAccountIDs.size !== 2) { - return false; - } - - // by email participants - const baseRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN + "$"); - const teamRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN + "$"); - const participantsContext = useContext(PersonalDetailsContext); - - for (const participantAccountID of participantAccountIDs){ - let id = Number(participantAccountID); - let contextAccountData = participantsContext[id]; - if(!contextAccountData){ - continue - } - if(baseRegexp.test(contextAccountData.login)){ - return true - } - if(teamRegexp.test(contextAccountData.login)){ - return true - } - } - - // System users communication - const expensifyTeam = new Set(Object.values(CONST.ACCOUNT_ID)); - const expensifyTeamParticipants = expensifyTeam.intersection(participantAccountIDs) - if (expensifyTeamParticipants.size > 0){ - return true; - } - - return false + if (!report?.participants || isThread(report)) { + return false; + } + + const participantAccountIDs = new Set(Object.keys(report.participants)); + if (participantAccountIDs.size !== 2) { + return false; + } + + // by email participants + const baseRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN + '$'); + const teamRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN + '$'); + const participantsContext = useContext(PersonalDetailsContext); + + for (const participantAccountID of participantAccountIDs) { + let id = Number(participantAccountID); + let contextAccountData = participantsContext[id]; + if (!contextAccountData) { + continue; + } + if (baseRegexp.test(contextAccountData.login)) { + return true; + } + if (teamRegexp.test(contextAccountData.login)) { + return true; + } + } + + // System users communication + const expensifyTeam = new Set(Object.values(CONST.ACCOUNT_ID)); + const expensifyTeamParticipants = expensifyTeam.intersection(participantAccountIDs); + if (expensifyTeamParticipants.size > 0) { + return true; + } + + return false; } export { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index ea63056b1cd0..28ca585289fa 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -44,6 +44,7 @@ import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; +import {parseFSAttributes} from '@libs/Fullstory'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; @@ -83,7 +84,6 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import {parseFSAttributes} from '@libs/Fullstory'; type ReportActionItemProps = { /** Report for this action */ diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index d63992ea9595..4a4d41086908 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -20,6 +20,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import DateUtils from '@libs/DateUtils'; +import {getChatFSAttributes, parseFSAttributes} from '@libs/Fullstory'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -36,7 +37,6 @@ import FloatingMessageCounter from './FloatingMessageCounter'; import getInitialNumToRender from './getInitialNumReportActionsToRender'; import ListBoundaryLoader from './ListBoundaryLoader'; import ReportActionsListItemRenderer from './ReportActionsListItemRenderer'; -import {parseFSAttributes, getChatFSAttributes} from '@libs/Fullstory'; type LoadNewerChats = DebouncedFunc<(params: {distanceFromStart: number}) => void>; @@ -716,9 +716,11 @@ function ReportActionsList({ isActive={(isFloatingMessageCounterVisible && !!unreadMarkerReportActionID) || canScrollToNewerComments} onClick={scrollToBottomAndMarkReportAsRead} /> - + Date: Thu, 31 Oct 2024 14:17:00 +0000 Subject: [PATCH 03/20] #EX-4 Removed unused imports --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2ebf7b0daad9..a5c05151845c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -18,7 +18,7 @@ import {FallbackAvatar, IntacctSquare, NetSuiteSquare, QBOSquare, XeroSquare} fr import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; -import {PersonalDetailsContext, PersonalDetailsProvider} from '@src/components/OnyxProvider'; +import {PersonalDetailsContext} from '@src/components/OnyxProvider'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; From 74f035437f9ceb3136dfe7afba6cd24cd42d8c4f Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Tue, 5 Nov 2024 11:06:38 +0000 Subject: [PATCH 04/20] #EX-4 Refactoring, context evaluation moved to component level. Changed function args usage in related functions. --- src/libs/Fullstory/index.native.ts | 9 ++--- src/libs/Fullstory/index.ts | 11 +++--- src/libs/ReportUtils.ts | 40 ++++++++++----------- src/pages/home/report/ReportActionItem.tsx | 2 +- src/pages/home/report/ReportActionsList.tsx | 8 +++-- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 9f5a0cbb334d..02c069020226 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,9 +1,10 @@ import FullStory, {FSPage} from '@fullstory/react-native'; +import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; -import type {OnyxInputOrEntry, Report, UserMetadata} from '@src/types/onyx'; +import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; const MASK = 'fs-mask'; const UNMASK = 'fs-unmask'; @@ -82,7 +83,7 @@ function parseFSAttributes(): void { in case data-test-id attribute usage, clean component name should be preserved in data-test-id. */ -function getFSAttributes(name: string, mask: bool, prefix: bool): string { +function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { // prefixed for Native apps should contain only component name if (prefix) { return name; @@ -97,7 +98,7 @@ function getFSAttributes(name: string, mask: bool, prefix: bool): string { return fsAttrValue; } -function getChatFSAttributes(name: string, report: OnyxInputOrEntry, prefix: bool): string { +function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry, prefix: boolean): string { // prefixed for Native apps should contain only component name if (prefix) { return name; @@ -112,7 +113,7 @@ function getChatFSAttributes(name: string, report: OnyxInputOrEntry, pre fs-unmask,fs-concierge-chatMessage */ fsAttrValue = `${UNMASK}${componentName}`; - } else if (!!isExpensifyAndCustomerChat(report)) { + } else if (!!isExpensifyAndCustomerChat(context, report)) { componentName = name ? `,fs-${CUSTOMER}-${name}` : ''; /* fs-mask,fs-customer-chatMessage diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 71acc1a16029..709fd00cc36d 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,9 +1,10 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; +import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; -import type {OnyxInputOrEntry, Report, UserMetadata} from '@src/types/onyx'; +import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; import type NavigationProperties from './types'; const WEB_PROP_ATTR = 'data-testid'; @@ -108,7 +109,7 @@ const FS = { */ function parseFSAttributes(): void { window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { - let attr = o.getAttribute(WEB_PROP_ATTR); + let attr = o.getAttribute(WEB_PROP_ATTR) || ''; if (/fs\-/gim.test(attr)) { let fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) || []; o.setAttribute('fs-class', fsAttrs.join(',')); @@ -135,7 +136,7 @@ function parseFSAttributes(): void { in case data-test-id attribute usage, clean component name should be preserved in data-test-id. */ -function getFSAttributes(name: string, mask: bool, prefix: bool): string { +function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { const componentPrefix = prefix ? `${name},` : ''; const componentSuffix = name ? `,fs-${name}` : ''; const fsAttrValue = `${componentPrefix}${mask ? MASK : UNMASK}${componentSuffix}`; @@ -146,7 +147,7 @@ function getFSAttributes(name: string, mask: bool, prefix: bool): string { return fsAttrValue; } -function getChatFSAttributes(name: string, report: OnyxInputOrEntry, prefix: bool): string { +function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry, prefix: boolean): string { let componentPrefix = prefix ? `${name},` : ''; let componentSuffix = name ? `,fs-${name}` : ''; let fsAttrValue = ''; @@ -158,7 +159,7 @@ function getChatFSAttributes(name: string, report: OnyxInputOrEntry, pre concierge-chatMessage,fs-unmask,fs-chatMessage */ fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; - } else if (!!isExpensifyAndCustomerChat(report)) { + } else if (!!isExpensifyAndCustomerChat(context, report)) { componentPrefix = prefix ? `${CUSTOMER}-${name},` : ''; componentSuffix = name ? `,fs-${name}` : ''; /* diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a5c05151845c..e9a74929547f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -18,7 +18,6 @@ import {FallbackAvatar, IntacctSquare, NetSuiteSquare, QBOSquare, XeroSquare} fr import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; -import {PersonalDetailsContext} from '@src/components/OnyxProvider'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; @@ -8409,7 +8408,7 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry) { return isExpenseReport(report) && report?.hasParentAccess === false; } -function isExpensifyAndCustomerChat(report: OnyxInputOrEntry): boolean { +function isExpensifyAndCustomerChat(participantsContext: OnyxEntry, report: OnyxInputOrEntry): boolean { if (!report?.participants || isThread(report)) { return false; } @@ -8418,30 +8417,31 @@ function isExpensifyAndCustomerChat(report: OnyxInputOrEntry): boolean { if (participantAccountIDs.size !== 2) { return false; } + if (!!participantsContext) { + // by email participants + const baseRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN + '$'); + const teamRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN + '$'); - // by email participants - const baseRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN + '$'); - const teamRegexp = new RegExp(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN + '$'); - const participantsContext = useContext(PersonalDetailsContext); - - for (const participantAccountID of participantAccountIDs) { - let id = Number(participantAccountID); - let contextAccountData = participantsContext[id]; - if (!contextAccountData) { - continue; - } - if (baseRegexp.test(contextAccountData.login)) { - return true; - } - if (teamRegexp.test(contextAccountData.login)) { - return true; + for (const participantAccountID of participantAccountIDs) { + let id = Number(participantAccountID); + let contextAccountData = participantsContext[id]; + if (!contextAccountData) { + continue; + } + let login = contextAccountData.login || ''; + if (baseRegexp.test(login)) { + return true; + } + if (teamRegexp.test(login)) { + return true; + } } } // System users communication const expensifyTeam = new Set(Object.values(CONST.ACCOUNT_ID)); - const expensifyTeamParticipants = expensifyTeam.intersection(participantAccountIDs); - if (expensifyTeamParticipants.size > 0) { + const hasIntersection = [...participantAccountIDs].some((id) => expensifyTeam.has(Number(id))); + if (hasIntersection) { return true; } diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 28ca585289fa..b24ebb4284a1 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -322,7 +322,7 @@ function ReportActionItem({ useEffect(() => { if (action && popoverAnchorRef.current) { - parseFSAttributes(popoverAnchorRef.current); + parseFSAttributes(); } }, [action]); diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 4a4d41086908..a4b290f36f9e 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -3,7 +3,7 @@ import {useIsFocused, useRoute} from '@react-navigation/native'; import type {RouteProp} from '@react-navigation/native'; // eslint-disable-next-line lodash/import-scope import type {DebouncedFunc} from 'lodash'; -import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; import {DeviceEventEmitter, InteractionManager, View} from 'react-native'; import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, StyleProp, ViewStyle} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -28,6 +28,7 @@ import Visibility from '@libs/Visibility'; import type {AuthScreensParamList} from '@navigation/types'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; +import {PersonalDetailsContext} from '@src/components/OnyxProvider'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -166,6 +167,7 @@ function ReportActionsList({ const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID ?? -1}`); const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID}); + const participantsContext = useContext(PersonalDetailsContext); useEffect(() => { const unsubscriber = Visibility.onVisibilityChange(() => { @@ -718,8 +720,8 @@ function ReportActionsList({ /> Date: Tue, 5 Nov 2024 11:19:38 +0000 Subject: [PATCH 05/20] #EX-4 ESLINT fixes --- src/libs/Fullstory/index.native.ts | 1 - src/libs/Fullstory/index.ts | 6 +++--- src/libs/ReportUtils.ts | 7 +++---- src/pages/home/report/ReportActionsList.tsx | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 02c069020226..42dc3a5b7c9c 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,5 +1,4 @@ import FullStory, {FSPage} from '@fullstory/react-native'; -import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 709fd00cc36d..7d9fa608440b 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -109,9 +109,9 @@ const FS = { */ function parseFSAttributes(): void { window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { - let attr = o.getAttribute(WEB_PROP_ATTR) || ''; - if (/fs\-/gim.test(attr)) { - let fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) || []; + const attr = o.getAttribute(WEB_PROP_ATTR) ?? ''; + if (/fs-/gim.test(attr)) { + let fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) ?? []; o.setAttribute('fs-class', fsAttrs.join(',')); let cleanedAttrs = attr; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e9a74929547f..119c58aa1cdd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -7,7 +7,6 @@ import isEmpty from 'lodash/isEmpty'; import lodashIsEqual from 'lodash/isEqual'; import isNumber from 'lodash/isNumber'; import lodashMaxBy from 'lodash/maxBy'; -import {useContext} from 'react'; import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {SvgProps} from 'react-native-svg'; @@ -8423,12 +8422,12 @@ function isExpensifyAndCustomerChat(participantsContext: OnyxEntry Date: Tue, 5 Nov 2024 11:59:02 +0000 Subject: [PATCH 06/20] #EX-4 ESLINT fixes --- src/libs/Fullstory/index.native.ts | 4 ++-- src/libs/Fullstory/index.ts | 37 +++++++++++++++++------------- src/libs/ReportUtils.ts | 23 +++++++++---------- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 42dc3a5b7c9c..059c41899ad2 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -106,13 +106,13 @@ function getChatFSAttributes(context: OnyxEntry, name: stri let componentName = name ? `,fs-${name}` : ''; let fsAttrValue = ''; - if (!!isConciergeChatReport(report)) { + if (isConciergeChatReport(report)) { componentName = name ? `,fs-${CONCIERGE}-${name}` : ''; /* fs-unmask,fs-concierge-chatMessage */ fsAttrValue = `${UNMASK}${componentName}`; - } else if (!!isExpensifyAndCustomerChat(context, report)) { + } else if (isExpensifyAndCustomerChat(context, report)) { componentName = name ? `,fs-${CUSTOMER}-${name}` : ''; /* fs-mask,fs-customer-chatMessage diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 7d9fa608440b..78107f244816 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,5 +1,4 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; -import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -110,24 +109,30 @@ const FS = { function parseFSAttributes(): void { window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { const attr = o.getAttribute(WEB_PROP_ATTR) ?? ''; - if (/fs-/gim.test(attr)) { - let fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) ?? []; - o.setAttribute('fs-class', fsAttrs.join(',')); + if (!/fs-/gim.test(attr)) { + return + } - let cleanedAttrs = attr; - fsAttrs.forEach((fsAttr) => { - cleanedAttrs = cleanedAttrs.replace(fsAttr, ''); - }); + const fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) ?? []; + o.setAttribute('fs-class', fsAttrs.join(',')); - cleanedAttrs = cleanedAttrs - .replace(/,+/g, ',') - .replace(/\s*,\s*/g, ',') - .replace(/^,+|,+$/g, '') - .replace(/\s+/g, ' ') - .trim(); + let cleanedAttrs = attr; + fsAttrs.forEach((fsAttr) => { + cleanedAttrs = cleanedAttrs.replace(fsAttr, ''); + }); - cleanedAttrs ? o.setAttribute(WEB_PROP_ATTR, cleanedAttrs) : o.removeAttribute(WEB_PROP_ATTR); - } + cleanedAttrs = cleanedAttrs + .replace(/,+/g, ',') + .replace(/\s*,\s*/g, ',') + .replace(/^,+|,+$/g, '') + .replace(/\s+/g, ' ') + .trim(); + + if(cleanedAttrs){ + o.setAttribute(WEB_PROP_ATTR, cleanedAttrs) + } else { + o.removeAttribute(WEB_PROP_ATTR) + }; }); } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 119c58aa1cdd..dfa19e6320c6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8416,23 +8416,22 @@ function isExpensifyAndCustomerChat(participantsContext: OnyxEntry Date: Tue, 5 Nov 2024 12:01:29 +0000 Subject: [PATCH 07/20] #EX-4 ESLINT fixes Prefer an early return to a conditionally-wrapped function body rulesdir/prefer-early-return --- src/pages/home/report/ReportActionItem.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index b24ebb4284a1..a21c45de6dad 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -321,9 +321,11 @@ function ReportActionItem({ }, [latestDecision, action]); useEffect(() => { - if (action && popoverAnchorRef.current) { - parseFSAttributes(); + const isCurrentChange = action && popoverAnchorRef.current; + if (!isCurrentChange) { + return } + parseFSAttributes(); }, [action]); const toggleContextMenuFromActiveReportAction = useCallback(() => { From 0894527dc79ba43566d5c0627abc79186b7a5e9a Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Tue, 5 Nov 2024 12:24:49 +0000 Subject: [PATCH 08/20] #EX-4 Prettier fixes --- src/libs/Fullstory/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 78107f244816..6e04ff841839 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -110,7 +110,7 @@ function parseFSAttributes(): void { window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { const attr = o.getAttribute(WEB_PROP_ATTR) ?? ''; if (!/fs-/gim.test(attr)) { - return + return; } const fsAttrs = attr.match(/fs-[a-zA-Z0-9_-]+/g) ?? []; @@ -128,11 +128,11 @@ function parseFSAttributes(): void { .replace(/\s+/g, ' ') .trim(); - if(cleanedAttrs){ - o.setAttribute(WEB_PROP_ATTR, cleanedAttrs) + if (cleanedAttrs) { + o.setAttribute(WEB_PROP_ATTR, cleanedAttrs); } else { - o.removeAttribute(WEB_PROP_ATTR) - }; + o.removeAttribute(WEB_PROP_ATTR); + } }); } From f6e387f318d28d15250ecedfec75ed6ccfad92ee Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Tue, 5 Nov 2024 12:25:03 +0000 Subject: [PATCH 09/20] #EX-4 Prettier fixes --- src/pages/home/report/ReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a21c45de6dad..2bf30c96446a 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -323,7 +323,7 @@ function ReportActionItem({ useEffect(() => { const isCurrentChange = action && popoverAnchorRef.current; if (!isCurrentChange) { - return + return; } parseFSAttributes(); }, [action]); From 46d994d6b47e9a5cb2583e86e384735a21972411 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Tue, 5 Nov 2024 12:28:08 +0000 Subject: [PATCH 10/20] #EX-4 ESlint double neg check fixes --- src/libs/Fullstory/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 6e04ff841839..c10d8b52eda4 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -157,14 +157,14 @@ function getChatFSAttributes(context: OnyxEntry, name: stri let componentSuffix = name ? `,fs-${name}` : ''; let fsAttrValue = ''; - if (!!isConciergeChatReport(report)) { + if (isConciergeChatReport(report)) { componentPrefix = prefix ? `${CONCIERGE}-${name},` : ''; componentSuffix = name ? `,fs-${name}` : ''; /* concierge-chatMessage,fs-unmask,fs-chatMessage */ fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; - } else if (!!isExpensifyAndCustomerChat(context, report)) { + } else if (isExpensifyAndCustomerChat(context, report)) { componentPrefix = prefix ? `${CUSTOMER}-${name},` : ''; componentSuffix = name ? `,fs-${name}` : ''; /* From 85e7858783bc8dc169c25118090194b04b52ac4b Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 7 Nov 2024 18:13:17 +0000 Subject: [PATCH 11/20] #EX-4 Performance review fixes, Cleanup. --- src/CONST.ts | 1 + src/libs/Fullstory/index.native.ts | 47 ++++++------------- src/libs/Fullstory/index.ts | 50 +++++++-------------- src/libs/ReportUtils.ts | 23 ++++++---- src/pages/home/report/ReportActionItem.tsx | 9 ---- src/pages/home/report/ReportActionsList.tsx | 6 ++- 6 files changed, 50 insertions(+), 86 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d479eb666a0e..c2dfa9d30faf 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -304,6 +304,7 @@ const CONST = { // Note: Group and Self-DM excluded as these are not tied to a Workspace WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT, chatTypes.INVOICE], + WORKSPACE_REPORT_ROOM_TYPES: [chatTypes.POLICY_EXPENSE_CHAT], ANDROID_PACKAGE_NAME, WORKSPACE_ENABLE_FEATURE_REDIRECT_DELAY: 100, ANIMATED_HIGHLIGHT_ENTRY_DELAY: 50, diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 059c41899ad2..ff38d25634dd 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -83,50 +83,29 @@ function parseFSAttributes(): void { clean component name should be preserved in data-test-id. */ function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { + if (!name && !prefix) { + return `${mask ? MASK : UNMASK}`; + } // prefixed for Native apps should contain only component name if (prefix) { return name; } - const componentPrefix = prefix ? `${name},` : ''; - const componentSuffix = name ? `,fs-${name}` : ''; - const fsAttrValue = `${componentPrefix}${mask ? MASK : UNMASK}${componentSuffix}`; - /* - testID: componentName,fs-unmask,fs-componentName - fsClass: fs-unmask,fs-componentName - */ - return fsAttrValue; + + return `${name},${mask ? MASK : UNMASK}`; } -function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry, prefix: boolean): string { - // prefixed for Native apps should contain only component name - if (prefix) { - return name; +function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string { + if (!name) { + return ''; } - // default - let componentName = name ? `,fs-${name}` : ''; - let fsAttrValue = ''; - + let config = {prefix: OTHER, mask: MASK}; if (isConciergeChatReport(report)) { - componentName = name ? `,fs-${CONCIERGE}-${name}` : ''; - /* - fs-unmask,fs-concierge-chatMessage - */ - fsAttrValue = `${UNMASK}${componentName}`; + config = {prefix: CONCIERGE, mask: UNMASK}; } else if (isExpensifyAndCustomerChat(context, report)) { - componentName = name ? `,fs-${CUSTOMER}-${name}` : ''; - /* - fs-mask,fs-customer-chatMessage - */ - fsAttrValue = `${MASK}${componentName}`; - } else { - componentName = name ? `,fs-${OTHER}-${name}` : ''; - /* - fs-mask,fs-other-chatMessage - */ - fsAttrValue = `${MASK}${componentName}`; + config = {prefix: CUSTOMER, mask: UNMASK}; } - return fsAttrValue; + const formattedName = `${config.prefix}-${name}`; + return [`${formattedName}`, `${config.mask},${formattedName}`]; } - export default FS; export {FSPage, parseFSAttributes, getFSAttributes, getChatFSAttributes}; diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index c10d8b52eda4..0cfcdc337777 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -142,45 +142,29 @@ function parseFSAttributes(): void { clean component name should be preserved in data-test-id. */ function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { - const componentPrefix = prefix ? `${name},` : ''; - const componentSuffix = name ? `,fs-${name}` : ''; - const fsAttrValue = `${componentPrefix}${mask ? MASK : UNMASK}${componentSuffix}`; - /* - testID: componentName,fs-unmask,fs-componentName - fsClass: fs-unmask,fs-componentName - */ - return fsAttrValue; + if (!name) { + return `${mask ? MASK : UNMASK}`; + } + + if (prefix) { + return `${name},${mask ? MASK : UNMASK}`; + } + + return `${name}`; } -function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry, prefix: boolean): string { - let componentPrefix = prefix ? `${name},` : ''; - let componentSuffix = name ? `,fs-${name}` : ''; - let fsAttrValue = ''; +function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string { + if (!name) { + return ''; + } + let config = {prefix: OTHER, mask: MASK}; if (isConciergeChatReport(report)) { - componentPrefix = prefix ? `${CONCIERGE}-${name},` : ''; - componentSuffix = name ? `,fs-${name}` : ''; - /* - concierge-chatMessage,fs-unmask,fs-chatMessage - */ - fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; + config = {prefix: CONCIERGE, mask: UNMASK}; } else if (isExpensifyAndCustomerChat(context, report)) { - componentPrefix = prefix ? `${CUSTOMER}-${name},` : ''; - componentSuffix = name ? `,fs-${name}` : ''; - /* - customer-chatMessage,fs-unmask,fs-chatMessage - */ - fsAttrValue = `${componentPrefix}${UNMASK}${componentSuffix}`; - } else { - componentPrefix = prefix ? `${OTHER}-${name},` : ''; - componentSuffix = name ? `,fs-${name}` : ''; - /* - other-chatMessage,fs-mask,fs-chatMessage - */ - fsAttrValue = `${componentPrefix}${MASK}${componentSuffix}`; + config = {prefix: CUSTOMER, mask: UNMASK}; } - - return fsAttrValue; + return [`${config.prefix}-${name},${config.mask}`, `${config.prefix}-${name}`]; } export default FS; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index dfa19e6320c6..62d27656ebfb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8408,28 +8408,35 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry) { } function isExpensifyAndCustomerChat(participantsContext: OnyxEntry, report: OnyxInputOrEntry): boolean { - if (!report?.participants || isThread(report)) { - return false; + if (!report?.participants) { + return true; } const participantAccountIDs = new Set(Object.keys(report.participants)); - if (participantAccountIDs.size !== 2) { + if (participantAccountIDs.size > 2) { return false; } + + if (isThread(report) && CONST.WORKSPACE_REPORT_ROOM_TYPES.includes(report.chatType)) { + return true; + } + + if (isThread(report) && report.type === CONST.REPORT.TYPE.EXPENSE) { + return true; + } + if (participantsContext) { // by email participants - const baseRegexp = new RegExp(`${CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN}$`); - const teamRegexp = new RegExp(`${CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN}$`); - for (const participantAccountID of participantAccountIDs) { const id = Number(participantAccountID); const contextAccountData = participantsContext[id]; if (contextAccountData) { const login = contextAccountData.login ?? ''; - if (baseRegexp.test(login)) { + if (login.endsWith(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) { return true; } - if (teamRegexp.test(login)) { + + if (login.endsWith(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN)) { return true; } } diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 2bf30c96446a..a0e2f65a89a0 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -44,7 +44,6 @@ import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; -import {parseFSAttributes} from '@libs/Fullstory'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; @@ -320,14 +319,6 @@ function ReportActionItem({ setIsHidden(false); }, [latestDecision, action]); - useEffect(() => { - const isCurrentChange = action && popoverAnchorRef.current; - if (!isCurrentChange) { - return; - } - parseFSAttributes(); - }, [action]); - const toggleContextMenuFromActiveReportAction = useCallback(() => { setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); }, [action.reportActionID]); diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 782edbc474ff..a9cb1f053b12 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -712,6 +712,8 @@ function ReportActionsList({ // When performing comment linking, initially 25 items are added to the list. Subsequent fetches add 15 items from the cache or 50 items from the server. // This is to ensure that the user is able to see the 'scroll to newer comments' button when they do comment linking and have not reached the end of the list yet. const canScrollToNewerComments = !isLoadingInitialReportActions && !hasNewestReportAction && sortedReportActions.length > 25 && !isLastPendingActionIsDelete; + const [reportActionsListTestID, reportActionsListFSClass] = getChatFSAttributes(participantsContext, 'ReportActionsList', report); + return ( <> Date: Thu, 7 Nov 2024 21:23:05 +0000 Subject: [PATCH 12/20] #EX-4 Performance review fixes --- src/libs/Fullstory/index.native.ts | 16 +++++++++------- src/libs/Fullstory/index.ts | 17 ++++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index ff38d25634dd..1e8af94c9c6b 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -94,18 +94,20 @@ function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { return `${name},${mask ? MASK : UNMASK}`; } -function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string { +function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string[] { if (!name) { return ''; } - let config = {prefix: OTHER, mask: MASK}; if (isConciergeChatReport(report)) { - config = {prefix: CONCIERGE, mask: UNMASK}; - } else if (isExpensifyAndCustomerChat(context, report)) { - config = {prefix: CUSTOMER, mask: UNMASK}; + const formattedName = `${CONCIERGE}-${name}`; + return [`${formattedName}`, `${UNMASK},${formattedName}`]; } - const formattedName = `${config.prefix}-${name}`; - return [`${formattedName}`, `${config.mask},${formattedName}`]; + if (isExpensifyAndCustomerChat(context, report)) { + const formattedName = `${CUSTOMER}-${name}`; + return [`${formattedName}`, `${UNMASK},${formattedName}`]; + } + const formattedName = `${OTHER}-${name}`; + return [`${formattedName}`, `${MASK},${formattedName}`]; } export default FS; export {FSPage, parseFSAttributes, getFSAttributes, getChatFSAttributes}; diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 0cfcdc337777..086eaad5a3ec 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -153,18 +153,21 @@ function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { return `${name}`; } -function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string { +function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string[] { if (!name) { return ''; } - let config = {prefix: OTHER, mask: MASK}; - if (isConciergeChatReport(report)) { - config = {prefix: CONCIERGE, mask: UNMASK}; - } else if (isExpensifyAndCustomerChat(context, report)) { - config = {prefix: CUSTOMER, mask: UNMASK}; + const formattedName = `${CONCIERGE}-${name}`; + return [`${formattedName},${UNMASK}`, `${formattedName}`]; + } + if (isExpensifyAndCustomerChat(context, report)) { + const formattedName = `${CUSTOMER}-${name}`; + return [`${formattedName},${UNMASK}`, `${formattedName}`]; } - return [`${config.prefix}-${name},${config.mask}`, `${config.prefix}-${name}`]; + + const formattedName = `${OTHER}-${name}`; + return [`${formattedName},${MASK}`, `${formattedName}`]; } export default FS; From 74abee5fcfac2bd3a9324c79eeec30db25f20d6f Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Fri, 8 Nov 2024 12:45:05 +0000 Subject: [PATCH 13/20] #EX-4 TScheck, Prettier, lint fixes --- src/CONST.ts | 1 - src/libs/Fullstory/index.native.ts | 2 +- src/libs/Fullstory/index.ts | 2 +- src/libs/ReportUtils.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index c2dfa9d30faf..d479eb666a0e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -304,7 +304,6 @@ const CONST = { // Note: Group and Self-DM excluded as these are not tied to a Workspace WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT, chatTypes.INVOICE], - WORKSPACE_REPORT_ROOM_TYPES: [chatTypes.POLICY_EXPENSE_CHAT], ANDROID_PACKAGE_NAME, WORKSPACE_ENABLE_FEATURE_REDIRECT_DELAY: 100, ANIMATED_HIGHLIGHT_ENTRY_DELAY: 50, diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index 1e8af94c9c6b..bf023de98ff5 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -96,7 +96,7 @@ function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string[] { if (!name) { - return ''; + return ['', '']; } if (isConciergeChatReport(report)) { const formattedName = `${CONCIERGE}-${name}`; diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 086eaad5a3ec..08c102242b62 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -155,7 +155,7 @@ function getFSAttributes(name: string, mask: boolean, prefix: boolean): string { function getChatFSAttributes(context: OnyxEntry, name: string, report: OnyxInputOrEntry): string[] { if (!name) { - return ''; + return ['', '']; } if (isConciergeChatReport(report)) { const formattedName = `${CONCIERGE}-${name}`; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 62d27656ebfb..91be9ecd261e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8417,7 +8417,7 @@ function isExpensifyAndCustomerChat(participantsContext: OnyxEntry Date: Fri, 8 Nov 2024 17:33:42 +0000 Subject: [PATCH 14/20] #EX-4 Better function naming. --- src/libs/Fullstory/index.native.ts | 4 ++-- src/libs/Fullstory/index.ts | 4 ++-- src/libs/ReportUtils.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/Fullstory/index.native.ts b/src/libs/Fullstory/index.native.ts index bf023de98ff5..0ba2fc222f67 100644 --- a/src/libs/Fullstory/index.native.ts +++ b/src/libs/Fullstory/index.native.ts @@ -1,6 +1,6 @@ import FullStory, {FSPage} from '@fullstory/react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; +import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; @@ -102,7 +102,7 @@ function getChatFSAttributes(context: OnyxEntry, name: stri const formattedName = `${CONCIERGE}-${name}`; return [`${formattedName}`, `${UNMASK},${formattedName}`]; } - if (isExpensifyAndCustomerChat(context, report)) { + if (shouldUnmaskChat(context, report)) { const formattedName = `${CUSTOMER}-${name}`; return [`${formattedName}`, `${UNMASK},${formattedName}`]; } diff --git a/src/libs/Fullstory/index.ts b/src/libs/Fullstory/index.ts index 08c102242b62..563ef200ea11 100644 --- a/src/libs/Fullstory/index.ts +++ b/src/libs/Fullstory/index.ts @@ -1,6 +1,6 @@ import {FullStory, init, isInitialized} from '@fullstory/browser'; import type {OnyxEntry} from 'react-native-onyx'; -import {isConciergeChatReport, isExpensifyAndCustomerChat} from '@libs/ReportUtils'; +import {isConciergeChatReport, shouldUnmaskChat} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import * as Environment from '@src/libs/Environment/Environment'; import type {OnyxInputOrEntry, PersonalDetailsList, Report, UserMetadata} from '@src/types/onyx'; @@ -161,7 +161,7 @@ function getChatFSAttributes(context: OnyxEntry, name: stri const formattedName = `${CONCIERGE}-${name}`; return [`${formattedName},${UNMASK}`, `${formattedName}`]; } - if (isExpensifyAndCustomerChat(context, report)) { + if (shouldUnmaskChat(context, report)) { const formattedName = `${CUSTOMER}-${name}`; return [`${formattedName},${UNMASK}`, `${formattedName}`]; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 91be9ecd261e..45c627a340b0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8407,7 +8407,7 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry) { return isExpenseReport(report) && report?.hasParentAccess === false; } -function isExpensifyAndCustomerChat(participantsContext: OnyxEntry, report: OnyxInputOrEntry): boolean { +function shouldUnmaskChat(participantsContext: OnyxEntry, report: OnyxInputOrEntry): boolean { if (!report?.participants) { return true; } @@ -8645,7 +8645,7 @@ export { isClosedExpenseReportWithNoExpenses, isCompletedTaskReport, isConciergeChatReport, - isExpensifyAndCustomerChat, + shouldUnmaskChat, isControlPolicyExpenseChat, isControlPolicyExpenseReport, isCurrentUserSubmitter, From ad347d27788fe4814f31988d56c624effe8b847f Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Fri, 8 Nov 2024 17:36:39 +0000 Subject: [PATCH 15/20] #EX-4 Higher priority to unmask policy expense chats. --- src/libs/ReportUtils.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 45c627a340b0..07078e5bd259 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8412,11 +8412,6 @@ function shouldUnmaskChat(participantsContext: OnyxEntry, r return true; } - const participantAccountIDs = new Set(Object.keys(report.participants)); - if (participantAccountIDs.size > 2) { - return false; - } - if (isThread(report) && report.chatType && report.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) { return true; } @@ -8425,6 +8420,11 @@ function shouldUnmaskChat(participantsContext: OnyxEntry, r return true; } + const participantAccountIDs = new Set(Object.keys(report.participants)); + if (participantAccountIDs.size > 2) { + return false; + } + if (participantsContext) { // by email participants for (const participantAccountID of participantAccountIDs) { From d8fc78f7aedea278e1ec6b56d32f2b88e649179b Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Fri, 8 Nov 2024 18:00:12 +0000 Subject: [PATCH 16/20] #EX-4 Internal 1:1 team chat masked. System users check masked. --- src/libs/ReportUtils.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 07078e5bd259..8a68fa731a51 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8427,27 +8427,26 @@ function shouldUnmaskChat(participantsContext: OnyxEntry, r if (participantsContext) { // by email participants + let teamInChat = false; + let userInChat = false; for (const participantAccountID of participantAccountIDs) { const id = Number(participantAccountID); const contextAccountData = participantsContext[id]; + if (contextAccountData) { const login = contextAccountData.login ?? ''; - if (login.endsWith(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN)) { - return true; - } - if (login.endsWith(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN)) { - return true; + if (login.endsWith(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN) ?? login.endsWith(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN)) { + teamInChat = true; + } else { + userInChat = true; } } } - } - - // System users communication - const expensifyTeam = new Set(Object.values(CONST.ACCOUNT_ID)); - const hasIntersection = [...participantAccountIDs].some((id) => expensifyTeam.has(Number(id))); - if (hasIntersection) { - return true; + // exclude teamOnly chat + if (teamInChat && userInChat) { + return true; + } } return false; From f905148a17d43c98acca1f73387d81376b4a8a6f Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 14 Nov 2024 09:40:42 +0000 Subject: [PATCH 17/20] #EX-4 clean unnecessary type coercion --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a68fa731a51..92f204f566aa 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8436,7 +8436,7 @@ function shouldUnmaskChat(participantsContext: OnyxEntry, r if (contextAccountData) { const login = contextAccountData.login ?? ''; - if (login.endsWith(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN) ?? login.endsWith(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN)) { + if (login.endsWith(CONST.EMAIL.EXPENSIFY_EMAIL_DOMAIN) || login.endsWith(CONST.EMAIL.EXPENSIFY_TEAM_EMAIL_DOMAIN)) { teamInChat = true; } else { userInChat = true; From 58ee1b9640580397ae5c17359a70ceb1aaca1d6a Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Thu, 14 Nov 2024 16:37:36 +0000 Subject: [PATCH 18/20] #EX-4 Error fix: Prettier diff detected --- src/pages/home/report/ReportActionsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 4ad89e462a8f..8e098fd78f9e 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -20,8 +20,8 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import DateUtils from '@libs/DateUtils'; -import isSearchTopmostCentralPane from '@libs/Navigation/isSearchTopmostCentralPane'; import {getChatFSAttributes} from '@libs/Fullstory'; +import isSearchTopmostCentralPane from '@libs/Navigation/isSearchTopmostCentralPane'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportConnection from '@libs/ReportConnection'; From c955f0dfbfe767096d3f93dfccf247d46adc21e1 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Sun, 1 Dec 2024 21:49:12 +0000 Subject: [PATCH 19/20] #EX-4 Error fix: Perf-test missing mocked library fix. --- tests/perf-test/ReportActionsList.perf-test.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/perf-test/ReportActionsList.perf-test.tsx b/tests/perf-test/ReportActionsList.perf-test.tsx index f7debc510832..3c510950b5c0 100644 --- a/tests/perf-test/ReportActionsList.perf-test.tsx +++ b/tests/perf-test/ReportActionsList.perf-test.tsx @@ -24,6 +24,16 @@ type LazyLoadLHNTestUtils = { const mockedNavigate = jest.fn(); +// Mock Fullstory library dependency +jest.mock('@libs/Fullstory', () => ({ + __esModule: true, + default: { + consentAndIdentify: jest.fn(), + }, + getFSAttributes: jest.fn(), + getChatFSAttributes: jest.fn().mockReturnValue(['mockTestID', 'mockFSClass']), +})); + jest.mock('@components/withCurrentUserPersonalDetails', () => { // Lazy loading of LHNTestUtils const lazyLoadLHNTestUtils = () => require('../utils/LHNTestUtils'); From ffe1c8affc74a08656cfb5680805612938b478c9 Mon Sep 17 00:00:00 2001 From: Oleksii Vagin Date: Sun, 1 Dec 2024 22:07:10 +0000 Subject: [PATCH 20/20] #EX-4 Eslint check fix. --- tests/perf-test/ReportActionsList.perf-test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/perf-test/ReportActionsList.perf-test.tsx b/tests/perf-test/ReportActionsList.perf-test.tsx index 3c510950b5c0..c52f8e9bf461 100644 --- a/tests/perf-test/ReportActionsList.perf-test.tsx +++ b/tests/perf-test/ReportActionsList.perf-test.tsx @@ -26,7 +26,6 @@ const mockedNavigate = jest.fn(); // Mock Fullstory library dependency jest.mock('@libs/Fullstory', () => ({ - __esModule: true, default: { consentAndIdentify: jest.fn(), },