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

[No QA] Handle Moderated Messages in UI #19476

Merged
merged 16 commits into from
May 30, 2023
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 @@ -1400,4 +1400,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 @@ -1866,4 +1866,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 @@ -105,6 +106,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);
Copy link
Member

@rushatgabhane rushatgabhane Aug 2, 2023

Choose a reason for hiding this comment

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

Hi, we missed a small offline edge case in this PR - #20946

On deleting a hidden message, the reveal message button should have been removed too.
More details here #20946 (comment)

const textInputRef = useRef();
const popoverAnchorRef = useRef();

Expand All @@ -117,6 +120,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)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (!props.action.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || _.isEmpty(props.action.message[0].moderationDecisions)) {
if (props.action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT || _.isEmpty(props.action.message[0].moderationDecisions)) {

NAB, it was just a bit confusing to read at first.

return;
}

// Right now we are only sending the latest moderationDecision to the frontend even though it is an array
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this because any previous moderation decisions won't really be used in the frontend?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep! We originally were just going to send them all because it was simpler for the backend, but this made a little more sense for JS.

const latestDecision = props.action.message[0].moderationDecisions[0];
if (latestDecision.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || latestDecision.decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (latestDecision.decision === CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE || latestDecision.decision === CONST.MODERATION.MODERATOR_DECISION_HIDDEN) {
if (_.contains([CONST.MODERATION.MODERATOR_DECISION_PENDING_HIDE, CONST.MODERATION.MODERATOR_DECISION_HIDDEN], latestDecision.decision)) {

NAB

setIsHidden(true);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We fail to set a hidden false case here which causes a regression #22024 , For more details refer this proposal

}
setModerationDecision(latestDecision.decision);
}, [props.action.message, props.action.actionName]);
Copy link
Contributor

Choose a reason for hiding this comment

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

✋ Coming from #20810

We're interested in the value of moderationDecisions[0].decision. We should change the dependency of the effect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

makes sense! we are also eventually switching this to only ever have one moderation decision, so we won't need this, but the fix in #20810 is great for now


const toggleContextMenuFromActiveReportAction = useCallback(() => {
setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID));
}, [props.action.reportActionID]);
Expand Down Expand Up @@ -227,6 +245,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 @@ -238,13 +257,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 @@ -260,6 +298,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'}
Copy link
Contributor

Choose a reason for hiding this comment

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

You forgot to translate this which caused a regression #20090

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, saw that one when it happened... I changed it in one of the places it appears but not the other one. Thanks!

</Text>
</Button>
)}
</ShowContextMenuContext.Provider>
);
}
Expand Down Expand Up @@ -319,6 +371,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 @@ -45,6 +45,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 @@ -53,6 +56,7 @@ const defaultProps = {
wrapperStyles: [styles.chatItem],
showHeader: true,
shouldShowSubscriptAvatar: false,
hasBeenFlagged: false,
report: undefined,
};

Expand Down Expand Up @@ -129,7 +133,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 @@ -1018,6 +1018,10 @@ const styles = {
lineHeight: 16,
},

lh20: {
lineHeight: 20,
},

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