-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
base: main
Are you sure you want to change the base?
Ex4 chat masking #51790
Changes from 13 commits
34fc112
7d20ce8
cd31436
74f0354
41f0c6a
d8282a6
08b15e5
0894527
f6e387f
46d994d
85e7858
9b2a46f
74abee5
01cf070
ad347d2
d8fc78f
f905148
2cfb212
58ee1b9
c955f0d
ffe1c8a
b8618bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1623,6 +1623,7 @@ const CONST = { | |
STUDENT_AMBASSADOR: '[email protected]', | ||
SVFG: '[email protected]', | ||
EXPENSIFY_EMAIL_DOMAIN: '@expensify.com', | ||
EXPENSIFY_TEAM_EMAIL_DOMAIN: '@team.expensify.com', | ||
}, | ||
|
||
CONCIERGE_DISPLAY_NAME: 'Concierge', | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,18 @@ | ||
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} 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'; | ||
const MASK = 'fs-mask'; | ||
const UNMASK = 'fs-unmask'; | ||
const CUSTOMER = 'customer'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
private pageName; | ||
|
@@ -16,7 +24,9 @@ class FSPage { | |
this.properties = properties; | ||
} | ||
|
||
start() {} | ||
start() { | ||
parseFSAttributes(); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -92,5 +102,73 @@ const FS = { | |
init: (_value: OnyxEntry<UserMetadata>) => {}, | ||
}; | ||
|
||
/** | ||
* Extract values from non-scraped at build time attribute WEB_PROP_ATTR, | ||
* reevaluate "fs-class". | ||
*/ | ||
function parseFSAttributes(): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also suggest to add test on this function, which seems complex and any future update should be cover by tests |
||
window?.document?.querySelectorAll(`[${WEB_PROP_ATTR}]`).forEach((o) => { | ||
const attr = o.getAttribute(WEB_PROP_ATTR) ?? ''; | ||
if (!/fs-/gim.test(attr)) { | ||
return; | ||
} | ||
|
||
const 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(); | ||
|
||
if (cleanedAttrs) { | ||
o.setAttribute(WEB_PROP_ATTR, cleanedAttrs); | ||
} else { | ||
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: boolean, prefix: boolean): string { | ||
if (!name) { | ||
return `${mask ? MASK : UNMASK}`; | ||
} | ||
|
||
if (prefix) { | ||
return `${name},${mask ? MASK : UNMASK}`; | ||
} | ||
|
||
return `${name}`; | ||
} | ||
|
||
function getChatFSAttributes(context: OnyxEntry<PersonalDetailsList>, name: string, report: OnyxInputOrEntry<Report>): string[] { | ||
if (!name) { | ||
return ['', '']; | ||
} | ||
if (isConciergeChatReport(report)) { | ||
const formattedName = `${CONCIERGE}-${name}`; | ||
return [`${formattedName},${UNMASK}`, `${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}; | ||
export {FSPage, parseFSAttributes, getFSAttributes, getChatFSAttributes}; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8407,6 +8407,52 @@ function isExpenseReportWithoutParentAccess(report: OnyxEntry<Report>) { | |||||
return isExpenseReport(report) && report?.hasParentAccess === false; | ||||||
} | ||||||
|
||||||
function isExpensifyAndCustomerChat(participantsContext: OnyxEntry<PersonalDetailsList>, report: OnyxInputOrEntry<Report>): boolean { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I don't think this is a correct name for this function. We're also checking if the chat is policyExpenseChat and returning true, and in those cases, it's not a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you. |
||||||
if (!report?.participants) { | ||||||
return true; | ||||||
} | ||||||
|
||||||
const participantAccountIDs = new Set(Object.keys(report.participants)); | ||||||
if (participantAccountIDs.size > 2) { | ||||||
return false; | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should come after the other checks. A police expense chat can have 10s of people, and we would want to unmask those too. this should be added after line 8426. |
||||||
|
||||||
if (isThread(report) && report.chatType && report.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I worry that the app will crash if we don't have any of those, won't it ? do you think that this is unnecessary ? |
||||||
return true; | ||||||
} | ||||||
|
||||||
if (isThread(report) && report.type === CONST.REPORT.TYPE.EXPENSE) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as ^ |
||||||
return true; | ||||||
} | ||||||
|
||||||
if (participantsContext) { | ||||||
// by email participants | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure if i understand this comment, can you be more specific here ? |
||||||
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; | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is wrong. If we have 1:1 chats between expensify users only, we don't want to unmask those. Since you'll only have 2 emails here, we only want to return true if one of the emails is from expensify AND the other isnt. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, got ot. |
||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
// 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; | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused on this part. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set.Intersection is not available in the TS version you are using. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have removed this bit as this was not part of original ticket. |
||||||
|
||||||
return false; | ||||||
} | ||||||
|
||||||
export { | ||||||
addDomainToShortMention, | ||||||
completeShortMention, | ||||||
|
@@ -8599,6 +8645,7 @@ export { | |||||
isClosedExpenseReportWithNoExpenses, | ||||||
isCompletedTaskReport, | ||||||
isConciergeChatReport, | ||||||
isExpensifyAndCustomerChat, | ||||||
isControlPolicyExpenseChat, | ||||||
isControlPolicyExpenseReport, | ||||||
isCurrentUserSubmitter, | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
|
@@ -20,13 +20,15 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; | |
import useThemeStyles from '@hooks/useThemeStyles'; | ||
import useWindowDimensions from '@hooks/useWindowDimensions'; | ||
import DateUtils from '@libs/DateUtils'; | ||
import {getChatFSAttributes} from '@libs/Fullstory'; | ||
import Navigation from '@libs/Navigation/Navigation'; | ||
import * as ReportActionsUtils from '@libs/ReportActionsUtils'; | ||
import * as ReportUtils from '@libs/ReportUtils'; | ||
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'; | ||
|
@@ -165,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(() => { | ||
|
@@ -709,13 +712,19 @@ 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. was comment from @gedu addressed here ? they mentioned that |
||
|
||
Comment on lines
+725
to
+726
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please address this comment too |
||
return ( | ||
<> | ||
<FloatingMessageCounter | ||
isActive={(isFloatingMessageCounterVisible && !!unreadMarkerReportActionID) || canScrollToNewerComments} | ||
onClick={scrollToBottomAndMarkReportAsRead} | ||
/> | ||
<View style={[styles.flex1, !shouldShowReportRecipientLocalTime && !hideComposer ? styles.pb4 : {}]}> | ||
<View | ||
style={[styles.flex1, !shouldShowReportRecipientLocalTime && !hideComposer ? styles.pb4 : {}]} | ||
testID={reportActionsListTestID} | ||
fsClass={reportActionsListFSClass} | ||
> | ||
<InvertedFlatList | ||
accessibilityLabel={translate('sidebarScreen.listOfChatMessages')} | ||
ref={reportScrollManager.ref} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay considering this placeholder, I will overlook it, but what is the use of this ?