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

Ex4 chat masking #51790

Closed
wants to merge 22 commits into from
Prev Previous commit
Next Next commit
#EX-4
Clean perfcheck passed.
  • Loading branch information
LCOleksii committed Nov 5, 2024
commit 7d20ce826140432d92f4d1a65b6fb745cdddb533
40 changes: 20 additions & 20 deletions src/libs/Fullstory/index.native.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -73,7 +73,7 @@ const FS = {
/**
* Placeholder function for Mobile-Web compatibility.
*/
function parseFSAttributes (): void{
function parseFSAttributes(): void {
// pass
}

Expand All @@ -82,44 +82,44 @@ 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
*/
return fsAttrValue;
}

function getChatFSAttributes (name: string, report: OnyxInputOrEntry<Report>, prefix: bool): string {
function getChatFSAttributes(name: string, report: OnyxInputOrEntry<Report>, 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
*/
Expand Down
60 changes: 30 additions & 30 deletions src/libs/Fullstory/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see these values repeated again from native and web files, this breaks the checklist checkbox which wants our code to be DRY, can you define these values in CONST.ts and use here and in native file ?

const CONCIERGE = 'concierge';
const OTHER = 'other';

// Placeholder Browser API does not support Manual Page definition
class FSPage {
Expand Down Expand Up @@ -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, ',')
Expand All @@ -123,51 +125,49 @@ 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);
}
})
});
}

/*
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}`;
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<Report>, prefix: bool): string {
let componentPrefix = prefix ? `${name},`:"";
let componentSuffix = name ? `,fs-${name}`:"";
let fsAttrValue = "";
function getChatFSAttributes(name: string, report: OnyxInputOrEntry<Report>, 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
*/
Expand Down
77 changes: 39 additions & 38 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -8409,42 +8410,42 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry<Report>) {
}

function isExpensifyAndCustomerChat(report: OnyxInputOrEntry<Report>): 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 {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/report/ReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 */
Expand Down
10 changes: 6 additions & 4 deletions src/pages/home/report/ReportActionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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>;

Expand Down Expand Up @@ -716,9 +716,11 @@ function ReportActionsList({
isActive={(isFloatingMessageCounterVisible && !!unreadMarkerReportActionID) || canScrollToNewerComments}
onClick={scrollToBottomAndMarkReportAsRead}
/>
<View style={[styles.flex1, !shouldShowReportRecipientLocalTime && !hideComposer ? styles.pb4 : {}]}
fsClass={getChatFSAttributes("ReportActionsList", report, false)}
testID={getChatFSAttributes("ReportActionsList", report, true)}>
<View
style={[styles.flex1, !shouldShowReportRecipientLocalTime && !hideComposer ? styles.pb4 : {}]}
fsClass={getChatFSAttributes('ReportActionsList', report, false)}
testID={getChatFSAttributes('ReportActionsList', report, true)}
>
<InvertedFlatList
accessibilityLabel={translate('sidebarScreen.listOfChatMessages')}
ref={reportScrollManager.ref}
Expand Down