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

New Feature: Create the UI for Actionable Mention Whispers #33665

Merged
merged 16 commits into from
Jan 13, 2024
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ const CONST = {
TASKCOMPLETED: 'TASKCOMPLETED',
TASKEDITED: 'TASKEDITED',
TASKREOPENED: 'TASKREOPENED',
ACTIONABLEMENTIONWHISPER: 'ACTIONABLEMENTIONWHISPER',
POLICYCHANGELOG: {
ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE',
ADD_BUDGET: 'POLICYCHANGELOG_ADD_BUDGET',
Expand Down Expand Up @@ -596,6 +597,10 @@ const CONST = {
},
THREAD_DISABLED: ['CREATED'],
},
ACTIONABLE_MENTION_WHISPER_RESOLUTION: {
INVITE: 'invited',
NOTHING: 'nothing',
},
ARCHIVE_REASON: {
DEFAULT: 'default',
ACCOUNT_CLOSED: 'accountClosed',
Expand Down
41 changes: 41 additions & 0 deletions src/components/ReportActionItem/ActionableItemButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import withLocalize, {WithLocalizeProps} from '@components/withLocalize';

Check failure on line 4 in src/components/ReportActionItem/ActionableItemButtons.tsx

View workflow job for this annotation

GitHub Actions / lint

Import "WithLocalizeProps" is only used as types
import useThemeStyles from '@hooks/useThemeStyles';
import {TranslationPaths} from '@src/languages/types';

Check failure on line 6 in src/components/ReportActionItem/ActionableItemButtons.tsx

View workflow job for this annotation

GitHub Actions / lint

All imports in the declaration are only used as types. Use `import type`

type ActionableItem = {
isPrimary?: boolean;
key: string;
onPress: () => void;
text: TranslationPaths;
};

type ActionableItemButtonsProps = WithLocalizeProps & {
items: ActionableItem[];
};

function ActionableItemButtons(props: ActionableItemButtonsProps) {
const styles = useThemeStyles();

return (
<View style={[styles.flexRow, styles.gap4]}>
{props.items?.map((item) => (
<Button
key={item.key}
style={[styles.mt2]}
onPress={item.onPress}
text={props.translate(item.text)}
small
success={item.isPrimary}
/>
))}
</View>
);
}

ActionableItemButtons.displayName = 'ActionableItemButtton';

export default withLocalize(ActionableItemButtons);
export type {ActionableItem};
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1933,6 +1933,10 @@ export default {
levelTwoResult: 'Message hidden from channel, plus anonymous warning and message is reported for review.',
levelThreeResult: 'Message removed from channel plus anonymous warning and message is reported for review.',
},
actionableMentionWhisperOptions: {
invite: 'Invite them',
nothing: 'Do nothing',
},
teachersUnitePage: {
teachersUnite: 'Teachers Unite',
joinExpensifyOrg: 'Join Expensify.org in eliminating injustice around the world and help teachers split their expenses for classrooms in need!',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2397,6 +2397,10 @@ export default {
copy: 'Copiar',
copied: '¡Copiado!',
},
actionableMentionWhisperOptions: {
invite: 'Invitar',
nothing: 'No hacer nada',
},
moderation: {
flagDescription: 'Todos los mensajes marcados se enviarán a un moderador para su revisión.',
chooseAReason: 'Elige abajo un motivo para reportarlo:',
Expand Down
57 changes: 57 additions & 0 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ function addActions(reportID: string, text = '', file?: File) {
reportComment?: string;
file?: File;
timezone?: string;
shouldAllowActionableMentionWhispers?: boolean;
clientCreatedTime?: string;
};

Expand All @@ -345,6 +346,7 @@ function addActions(reportID: string, text = '', file?: File) {
commentReportActionID: file && reportCommentAction ? reportCommentAction.reportActionID : null,
reportComment: reportCommentText,
file,
shouldAllowActionableMentionWhispers: true,
clientCreatedTime: file ? attachmentAction?.created : reportCommentAction?.created,
};

Expand Down Expand Up @@ -2546,6 +2548,60 @@ function clearNewRoomFormError() {
});
}

function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry<ReportAction>, resolution: ValueOf<typeof CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION>) {
const message = reportAction?.message?.[0];
if (!message) {
return;
}

const updatedMessage: Message = {
...message,
resolution,
};

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportId}`,
value: {
[reportAction.reportActionID]: {
message: [updatedMessage],
originalMessage: {
resolution,
},
jasperhuangg marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportId}`,
value: {
[reportAction.reportActionID]: {
message: [message],
originalMessage: {
resolution: null,
},
},
},
},
];

type ResolveActionableMentionWhisperParams = {
reportActionID: string;
resolution: ValueOf<typeof CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION>;
};

const parameters: ResolveActionableMentionWhisperParams = {
reportActionID: reportAction.reportActionID,
resolution,
};

API.write('ResolveActionableMentionWhisper', parameters, {optimisticData, failureData});
}

export {
searchInServer,
addComment,
Expand Down Expand Up @@ -2610,4 +2666,5 @@ export {
getDraftPrivateNote,
updateLastVisitTime,
clearNewRoomFormError,
resolveActionableMentionWhisper,
};
31 changes: 31 additions & 0 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import PressableWithSecondaryInteraction from '@components/PressableWithSecondar
import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes';
import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions';
import RenderHTML from '@components/RenderHTML';
import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons';
import ChronosOOOListActions from '@components/ReportActionItem/ChronosOOOListActions';
import MoneyReportView from '@components/ReportActionItem/MoneyReportView';
import MoneyRequestAction from '@components/ReportActionItem/MoneyRequestAction';
Expand Down Expand Up @@ -301,6 +302,25 @@ function ReportActionItem(props) {
[props.report, props.action, toggleContextMenuFromActiveReportAction],
);

const actionableItemButtons = useMemo(() => {
if (!(props.action.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLEMENTIONWHISPER && !lodashGet(props.action, 'originalMessage.resolution', null))) {
return [];
}
return [
{
text: 'actionableMentionWhisperOptions.invite',
key: `${props.action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`,
onPress: () => Report.resolveActionableMentionWhisper(props.report.reportID, props.action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE),
isPrimary: true,
},
{
text: 'actionableMentionWhisperOptions.nothing',
key: `${props.action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`,
onPress: () => Report.resolveActionableMentionWhisper(props.report.reportID, props.action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING),
},
];
}, [props.action, props.report]);

/**
* Get the content of ReportActionItem
* @param {Boolean} hovered whether the ReportActionItem is hovered
Expand Down Expand Up @@ -461,6 +481,17 @@ function ReportActionItem(props) {
</Text>
</Button>
)}
{/**
These are the actionable buttons that appear at the bottom of a Concierge message
for example: Invite a user mentioned but not a member of the room
https://github.com/Expensify/App/issues/32741
*/}
{actionableItemButtons.length > 0 && (
<ActionableItemButtons
action={props.action}
items={actionableItemButtons}
/>
)}
</View>
) : (
<ReportActionItemMessageEdit
Expand Down
14 changes: 14 additions & 0 deletions src/types/onyx/OriginalMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type OriginalMessageActionName =
| 'TASKCOMPLETED'
| 'TASKEDITED'
| 'TASKREOPENED'
| 'ACTIONABLEMENTIONWHISPER'
| ValueOf<typeof CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG>;
type OriginalMessageApproved = {
actionName: typeof CONST.REPORT.ACTIONS.TYPE.APPROVED;
Expand Down Expand Up @@ -109,6 +110,18 @@ type OriginalMessageAddComment = {
};
};

type OriginalMessageActionableMentionWhisper = {
actionName: typeof CONST.REPORT.ACTIONS.TYPE.ACTIONABLEMENTIONWHISPER;
originalMessage: {
inviteeAccountIDs: number[];
inviteeEmails: string;
lastModified: string;
reportID: number;
resolution?: ValueOf<typeof CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION> | null;
whisperedTo?: number[];
};
};

type OriginalMessageSubmitted = {
actionName: typeof CONST.REPORT.ACTIONS.TYPE.SUBMITTED;
originalMessage: unknown;
Expand Down Expand Up @@ -239,6 +252,7 @@ type OriginalMessage =
| OriginalMessageApproved
| OriginalMessageIOU
| OriginalMessageAddComment
| OriginalMessageActionableMentionWhisper
| OriginalMessageSubmitted
| OriginalMessageClosed
| OriginalMessageCreated
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/ReportAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type Message = {

/** ID of a task report */
taskReportID?: string;

/** resolution for actionable mention whisper */
resolution?: ValueOf<typeof CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION> | null;
};

type ImageMetadata = {
Expand Down
Loading