Skip to content

Commit

Permalink
Merge pull request #19476 from Expensify/dangrous-handlemoderatedmess…
Browse files Browse the repository at this point in the history
…ages

[No QA] Handle Moderated Messages in UI
  • Loading branch information
dangrous committed May 30, 2023
2 parents 2977c04 + 9bc5cd8 commit 619a529
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -2432,6 +2432,8 @@ const CONST = {
MODERATION: {
MODERATOR_DECISION_PENDING: 'pending',
MODERATOR_DECISION_PENDING_HIDE: 'pendingHide',
MODERATOR_DECISION_APPROVED: 'approved',
MODERATOR_DECISION_HIDDEN: 'hidden',
FLAG_SEVERITY_SPAM: 'spam',
FLAG_SEVERITY_INCONSIDERATE: 'inconsiderate',
},
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1405,4 +1405,9 @@ export default {
copyUrlToClipboard: 'Copy URL to clipboard',
copied: 'Copied!',
},
moderation: {
flaggedContent: 'This message has been flagged as violating our community rules and the content has been hidden.',
hideMessage: 'Hide message',
revealMessage: 'Reveal message',
},
};
5 changes: 5 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1871,4 +1871,9 @@ export default {
copyUrlToClipboard: 'Copiar URL al portapapeles',
copied: '¡Copiado!',
},
moderation: {
flaggedContent: 'Este mensaje ha sido marcado por violar las reglas de nuestra comunidad y el contenido se ha ocultado.',
hideMessage: 'Ocultar mensaje',
revealMessage: 'Revelar mensaje',
},
};
67 changes: 60 additions & 7 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import reportActionPropTypes from './reportActionPropTypes';
import * as StyleUtils from '../../../styles/StyleUtils';
import PressableWithSecondaryInteraction from '../../../components/PressableWithSecondaryInteraction';
import Hoverable from '../../../components/Hoverable';
import Button from '../../../components/Button';
import ReportActionItemSingle from './ReportActionItemSingle';
import ReportActionItemGrouped from './ReportActionItemGrouped';
import MoneyRequestAction from '../../../components/ReportActionItem/MoneyRequestAction';
Expand Down Expand Up @@ -107,6 +108,8 @@ const defaultProps = {

function ReportActionItem(props) {
const [isContextMenuActive, setIsContextMenuActive] = useState(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID));
const [isHidden, setIsHidden] = useState(false);
const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED);
const textInputRef = useRef();
const popoverAnchorRef = useRef();

Expand All @@ -119,6 +122,21 @@ function ReportActionItem(props) {
focusTextInputAfterAnimation(textInputRef.current, 100);
}, [isDraftEmpty]);

// Hide the message if it is being moderated for a higher offense, or is hidden by a moderator
// Removed messages should not be shown anyway and should not need this flow
useEffect(() => {
if (!props.action.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || _.isEmpty(props.action.message[0].moderationDecisions)) {
return;
}

// Right now we are only sending the latest moderationDecision to the frontend even though it is an array
const latestDecision = props.action.message[0].moderationDecisions[0];
if (latestDecision.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || latestDecision.decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN) {
setIsHidden(true);
}
setModerationDecision(latestDecision.decision);
}, [props.action.message, props.action.actionName]);

const toggleContextMenuFromActiveReportAction = useCallback(() => {
setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID));
}, [props.action.reportActionID]);
Expand Down Expand Up @@ -229,6 +247,7 @@ function ReportActionItem(props) {
);
} else {
const message = _.last(lodashGet(props.action, 'message', [{}]));
const hasBeenFlagged = !_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision);
const isAttachment = _.has(props.action, 'isAttachment') ? props.action.isAttachment : ReportUtils.isReportMessageAttachment(message);
children = (
<ShowContextMenuContext.Provider
Expand All @@ -240,13 +259,32 @@ function ReportActionItem(props) {
}}
>
{!props.draftMessage ? (
<ReportActionItemMessage
action={props.action}
style={[
!props.displayAsGroup && isAttachment ? styles.mt2 : undefined,
_.contains([..._.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), CONST.REPORT.ACTIONS.TYPE.IOU], props.action.actionName) ? styles.colorMuted : undefined,
]}
/>
<View style={props.displayAsGroup && hasBeenFlagged ? styles.blockquote : {}}>
<ReportActionItemMessage
action={props.action}
isHidden={isHidden}
style={[
!props.displayAsGroup && isAttachment ? styles.mt2 : undefined,
_.contains([..._.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), CONST.REPORT.ACTIONS.TYPE.IOU], props.action.actionName)
? styles.colorMuted
: undefined,
]}
/>
{props.displayAsGroup && hasBeenFlagged && (
<Button
small
style={[styles.mt2, styles.alignSelfStart]}
onPress={() => setIsHidden(!isHidden)}
>
<Text
style={styles.buttonSmallText}
selectable={false}
>
{isHidden ? props.translate('moderation.revealMessage') : props.translate('moderation.hideMessage')}
</Text>
</Button>
)}
</View>
) : (
<ReportActionItemMessageEdit
action={props.action}
Expand All @@ -262,6 +300,20 @@ function ReportActionItem(props) {
}
/>
)}
{!props.displayAsGroup && hasBeenFlagged && (
<Button
small
style={[styles.mt2, styles.alignSelfStart]}
onPress={() => setIsHidden(!isHidden)}
>
<Text
style={styles.buttonSmallText}
selectable={false}
>
{isHidden ? 'Reveal message' : 'Hide message'}
</Text>
</Button>
)}
</ShowContextMenuContext.Provider>
);
}
Expand Down Expand Up @@ -331,6 +383,7 @@ function ReportActionItem(props) {
wrapperStyles={[styles.chatItem, isWhisper ? styles.pt1 : {}]}
shouldShowSubscriptAvatar={props.shouldShowSubscriptAvatar}
report={props.report}
hasBeenFlagged={moderationDecision !== CONST.MODERATION.MODERATOR_DECISION_APPROVED && moderationDecision !== CONST.MODERATION.MODERATOR_DECISION_PENDING}
>
{content}
</ReportActionItemSingle>
Expand Down
34 changes: 21 additions & 13 deletions src/pages/home/report/ReportActionItemMessage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {View} from 'react-native';
import {View, Text} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import lodashGet from 'lodash/get';
Expand All @@ -15,28 +15,36 @@ const propTypes = {
/** Additional styles to add after local styles. */
style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),

/** Whether or not the message is hidden by moderation */
isHidden: PropTypes.bool,

/** localization props */
...withLocalizePropTypes,
};

const defaultProps = {
style: [],
isHidden: false,
};

const ReportActionItemMessage = (props) => (
<View style={[styles.chatItemMessage, ...props.style]}>
{_.map(_.compact(props.action.previousMessage || props.action.message), (fragment, index) => (
<ReportActionItemFragment
key={`actionFragment-${props.action.reportActionID}-${index}`}
fragment={fragment}
isAttachment={props.action.isAttachment}
attachmentInfo={props.action.attachmentInfo}
pendingAction={props.action.pendingAction}
source={lodashGet(props.action, 'originalMessage.source')}
loading={props.action.isLoading}
style={props.style}
/>
))}
{!props.isHidden ? (
_.map(_.compact(props.action.previousMessage || props.action.message), (fragment, index) => (
<ReportActionItemFragment
key={`actionFragment-${props.action.reportActionID}-${index}`}
fragment={fragment}
isAttachment={props.action.isAttachment}
attachmentInfo={props.action.attachmentInfo}
pendingAction={props.action.pendingAction}
source={lodashGet(props.action, 'originalMessage.source')}
loading={props.action.isLoading}
style={props.style}
/>
))
) : (
<Text style={[styles.textLabelSupporting, styles.lh20]}>{props.translate('moderation.flaggedContent')}</Text>
)}
</View>
);

Expand Down
6 changes: 5 additions & 1 deletion src/pages/home/report/ReportActionItemSingle.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ const propTypes = {
/** Determines if the avatar is displayed as a subscript (positioned lower than normal) */
shouldShowSubscriptAvatar: PropTypes.bool,

/** If the message has been flagged for moderation */
hasBeenFlagged: PropTypes.bool,

...withLocalizePropTypes,
};

Expand All @@ -54,6 +57,7 @@ const defaultProps = {
wrapperStyles: [styles.chatItem],
showHeader: true,
shouldShowSubscriptAvatar: false,
hasBeenFlagged: false,
report: undefined,
};

Expand Down Expand Up @@ -130,7 +134,7 @@ const ReportActionItemSingle = (props) => {
<ReportActionItemDate created={props.action.created} />
</View>
) : null}
{props.children}
<View style={props.hasBeenFlagged ? styles.blockquote : {}}>{props.children}</View>
</View>
</View>
);
Expand Down
4 changes: 4 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,10 @@ const styles = {
lineHeight: 16,
},

lh20: {
lineHeight: 20,
},

lh140Percent: {
lineHeight: '140%',
},
Expand Down

0 comments on commit 619a529

Please sign in to comment.