MM-61266 | MM-60948 - verified available actions on scheduled posts (#29106)

* verified available actions on scheduled posts

* Review fixes

* Added check for archived channel and deactivated DM
Этот коммит содержится в:
Harshil Sharma
2024-11-08 10:34:38 +05:30
коммит произвёл GitHub
родитель bd8774bdce
Коммит faa6853a28
9 изменённых файлов: 109 добавлений и 63 удалений

Просмотреть файл

@@ -58,6 +58,7 @@ import type {OnSubmitOptions, SubmitPostReturnType} from './views/create_comment
export type CreatePostOptions = {
keepDraft?: boolean;
ignorePostError?: boolean;
}
export function handleNewPost(post: Post, msg?: {data?: NewPostMessageProps & GroupChannel}): ActionFuncAsync<boolean, GlobalState> {

Просмотреть файл

@@ -126,7 +126,7 @@ const useSubmit = (
return;
}
if (postError) {
if (postError && !createPostOptions?.ignorePostError) {
setErrorClass('animation--highlight');
setTimeout(() => {
setErrorClass(null);

Просмотреть файл

@@ -7,7 +7,11 @@ import {withRouter} from 'react-router-dom';
import type {Channel} from '@mattermost/types/channels';
import {getCurrentChannel, getDirectTeammate, getMyChannelMembership} from 'mattermost-redux/selectors/entities/channels';
import {
getCurrentChannel,
getMyChannelMembership,
isDeactivatedDirectChannel,
} from 'mattermost-redux/selectors/entities/channels';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getRoles} from 'mattermost-redux/selectors/entities/roles_helpers';
import {getCurrentRelativeTeamUrl} from 'mattermost-redux/selectors/entities/teams';
@@ -21,12 +25,6 @@ import type {GlobalState} from 'types/store';
import ChannelView from './channel_view';
function isDeactivatedChannel(state: GlobalState, channelId: string) {
const teammate = getDirectTeammate(state, channelId);
return Boolean(teammate && teammate.delete_at);
}
function isMissingChannelRoles(state: GlobalState, channel?: Channel) {
const channelRoles = channel ? getMyChannelMembership(state, channel.id)?.roles || '' : '';
return !channelRoles.split(' ').some((v) => Boolean(getRoles(state)[v]));
@@ -45,7 +43,7 @@ function mapStateToProps(state: GlobalState) {
return {
channelId: channel ? channel.id : '',
deactivatedChannel: channel ? isDeactivatedChannel(state, channel.id) : false,
deactivatedChannel: channel ? isDeactivatedDirectChannel(state, channel.id) : false,
enableOnboardingFlow,
channelIsArchived: channel ? channel.delete_at !== 0 : false,
viewArchivedChannels,

Просмотреть файл

@@ -8,7 +8,7 @@ import {FormattedMessage, useIntl} from 'react-intl';
import {GenericModal} from '@mattermost/components';
type Props = {
channelDisplayName: string;
channelDisplayName?: string;
onConfirm: () => Promise<{error?: string}>;
onExited: () => void;
}
@@ -54,14 +54,25 @@ export default function DeleteScheduledPostModal({
autoCloseOnConfirmButton={false}
errorText={errorMessage}
>
<FormattedMessage
id={'scheduled_post.delete_modal.body'}
defaultMessage={'Are you sure you want to delete this scheduled post to <strong>{displayName}</strong>?'}
values={{
strong: (chunk: string) => <strong>{chunk}</strong>,
displayName: channelDisplayName,
}}
/>
{
channelDisplayName &&
<FormattedMessage
id={'scheduled_post.delete_modal.body'}
defaultMessage={'Are you sure you want to delete this scheduled post to <strong>{displayName}</strong>?'}
values={{
strong: (chunk: string) => <strong>{chunk}</strong>,
displayName: channelDisplayName,
}}
/>
}
{
!channelDisplayName &&
<FormattedMessage
id={'scheduled_post.delete_modal.body_no_channel'}
defaultMessage={'Are you sure you want to delete this scheduled post?'}
/>
}
</GenericModal>
);
}

Просмотреть файл

@@ -2,12 +2,15 @@
// See LICENSE.txt for license information.
import moment from 'moment';
import React, {memo, useCallback} from 'react';
import React, {memo, useCallback, useEffect} from 'react';
import {FormattedMessage} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux';
import type {Channel} from '@mattermost/types/channels';
import type {ScheduledPost} from '@mattermost/types/schedule_post';
import {fetchMissingChannels} from 'mattermost-redux/actions/channels';
import {isDeactivatedDirectChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone';
import {openModal} from 'actions/views/modals';
@@ -19,9 +22,10 @@ import DeleteScheduledPostModal
from 'components/drafts/draft_actions/schedule_post_actions/delete_scheduled_post_modal';
import SendDraftModal from 'components/drafts/draft_actions/send_draft_modal';
import {ModalIdentifiers} from 'utils/constants';
import Constants, {ModalIdentifiers} from 'utils/constants';
import './style.scss';
import type {GlobalState} from 'types/store';
const deleteTooltipText = (
<FormattedMessage
@@ -53,17 +57,28 @@ const sendNowTooltipText = (
type Props = {
scheduledPost: ScheduledPost;
channelDisplayName: string;
channel?: Channel;
onReschedule: (timestamp: number) => Promise<{error?: string}>;
onDelete: (scheduledPostId: string) => Promise<{error?: string}>;
onSend: (scheduledPostId: string) => void;
onEdit: () => void;
}
function ScheduledPostActions({scheduledPost, onReschedule, onDelete, channelDisplayName, onSend, onEdit}: Props) {
function ScheduledPostActions({scheduledPost, channel, onReschedule, onDelete, onSend, onEdit}: Props) {
const dispatch = useDispatch();
const userTimezone = useSelector(getCurrentTimezone);
useEffect(() => {
// this ensures the DM is loaded in redux store and is available
// later when we check if the DM is with a deactivated user.
if (channel?.type === Constants.DM_CHANNEL) {
// fetchMissingChannels uses DataLoader which de-duplicates all requested data,
// so even if we have multiple scheduled posts in a DM,
// the data loader ensured we fetch that DM only once.
dispatch(fetchMissingChannels([channel.id]));
}
}, [channel, dispatch]);
const handleReschedulePost = useCallback(() => {
const initialTime = moment.tz(scheduledPost.scheduled_at, userTimezone);
@@ -83,22 +98,34 @@ function ScheduledPostActions({scheduledPost, onReschedule, onDelete, channelDis
modalId: ModalIdentifiers.DELETE_DRAFT,
dialogType: DeleteScheduledPostModal,
dialogProps: {
channelDisplayName,
channelDisplayName: channel?.display_name,
onConfirm: () => onDelete(scheduledPost.id),
},
}));
}, [channelDisplayName, dispatch, onDelete, scheduledPost.id]);
}, [channel, dispatch, onDelete, scheduledPost.id]);
const handleSend = useCallback(() => {
if (!channel) {
return;
}
dispatch(openModal({
modalId: ModalIdentifiers.SEND_DRAFT,
dialogType: SendDraftModal,
dialogProps: {
displayName: channelDisplayName,
displayName: channel.display_name,
onConfirm: () => onSend(scheduledPost.id),
},
}));
}, [channelDisplayName, dispatch, onSend, scheduledPost.id]);
}, [channel, dispatch, onSend, scheduledPost.id]);
const showEditOption = !scheduledPost.error_code;
const isChannelArchived = Boolean(channel?.delete_at);
const isDeactivatedDM = useSelector((state: GlobalState) => isDeactivatedDirectChannel(state, scheduledPost.channel_id));
const showSendNowOption = (!scheduledPost.error_code || scheduledPost.error_code === 'unknown' || scheduledPost.error_code === 'unable_to_send') && channel && !isChannelArchived && !isDeactivatedDM;
const showRescheduleOption = !scheduledPost.error_code || scheduledPost.error_code === 'unknown' || scheduledPost.error_code === 'unable_to_send';
return (
<div className='ScheduledPostActions'>
@@ -111,36 +138,38 @@ function ScheduledPostActions({scheduledPost, onReschedule, onDelete, channelDis
/>
{
!scheduledPost.error_code && (
<React.Fragment>
<Action
icon='icon-pencil-outline'
id='edit'
name='edit'
tooltipText={editTooltipText}
onClick={onEdit}
showEditOption &&
<Action
icon='icon-pencil-outline'
id='edit'
name='edit'
tooltipText={editTooltipText}
onClick={onEdit}
/>
<Action
icon='icon-clock-send-outline'
id='reschedule'
name='reschedule'
tooltipText={rescheduleTooltipText}
onClick={handleReschedulePost}
/>
<Action
icon='icon-send-outline'
id='sendNow'
name='sendNow'
tooltipText={sendNowTooltipText}
onClick={handleSend}
/>
</React.Fragment>
)
/>
}
{
showRescheduleOption &&
<Action
icon='icon-clock-send-outline'
id='reschedule'
name='reschedule'
tooltipText={rescheduleTooltipText}
onClick={handleReschedulePost}
/>
}
{
showSendNowOption &&
<Action
icon='icon-send-outline'
id='sendNow'
name='sendNow'
tooltipText={sendNowTooltipText}
onClick={handleSend}
/>
}
</div>
);
}

Просмотреть файл

@@ -15,7 +15,7 @@ import type {UserProfile, UserStatus} from '@mattermost/types/users';
import {getPost as getPostAction} from 'mattermost-redux/actions/posts';
import {deleteScheduledPost, updateScheduledPost} from 'mattermost-redux/actions/scheduled_posts';
import {Permissions} from 'mattermost-redux/constants';
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {isDeactivatedDirectChannel, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
@@ -107,13 +107,17 @@ function DraftRow({
const connectionId = useSelector(getConnectionId);
const isChannelArchived = Boolean(channel?.delete_at);
const isDeactivatedDM = useSelector((state: GlobalState) => isDeactivatedDirectChannel(state, channelId));
let postError = '';
if (isScheduledPost) {
// This is applicable only for scheduled post.
if (item.error_code) {
postError = getErrorStringFromCode(intl, item.error_code);
postError = getErrorStringFromCode(intl, item.error_code);
} else if (isChannelArchived || isDeactivatedDM) {
postError = getErrorStringFromCode(intl, 'channel_archived');
}
} else if (rootPostDeleted) {
postError = intl.formatMessage({id: 'drafts.error.post_not_found', defaultMessage: 'Thread not found'});
@@ -279,19 +283,15 @@ function DraftRow({
isScheduledPostBeingSent.current = true;
const postDraft = scheduledPostToPostDraft(item as ScheduledPost);
handleOnSend(postDraft, undefined, {keepDraft: true});
handleOnSend(postDraft, undefined, {keepDraft: true, ignorePostError: true});
return Promise.resolve({});
}, [handleOnSend, item, handleCancelEdit]);
const scheduledPostActions = useMemo(() => {
if (!channel) {
return null;
}
return (
<ScheduledPostActions
scheduledPost={item as ScheduledPost}
channelDisplayName={channel.display_name}
channel={channel}
onReschedule={handleSchedulePostOnReschedule}
onDelete={handleSchedulePostOnDelete}
onSend={handleScheduledPostOnSend}

Просмотреть файл

@@ -50,5 +50,6 @@ const errorCodeToErrorMessage = defineMessages<ScheduledPostErrorCode>({
});
export function getErrorStringFromCode(intl: IntlShape, errorCode: ScheduledPostErrorCode = 'unknown') {
return intl.formatMessage(errorCodeToErrorMessage[errorCode]).toUpperCase();
const textDefinition = errorCodeToErrorMessage[errorCode] ?? errorCodeToErrorMessage.unknown;
return intl.formatMessage(textDefinition).toUpperCase();
}

Просмотреть файл

@@ -4868,6 +4868,7 @@
"scheduled_post.channel_indicator.single": "Message scheduled for {dateTime}.",
"scheduled_post.channel_indicator.with_other_user_late_time": "You have {count, plural, =1 {one} other {#}} <a>scheduled {count, plural, =1 {message} other {messages}}</a>.",
"scheduled_post.delete_modal.body": "Are you sure you want to delete this scheduled post to <strong>{displayName}</strong>?",
"scheduled_post.delete_modal.body_no_channel": "Are you sure you want to delete this scheduled post?",
"scheduled_post.delete_modal.title": "Delete scheduled post",
"scheduled_post.error_code.channel_archived": "Channel Archived",
"scheduled_post.error_code.channel_removed": "Channel Removed",

Просмотреть файл

@@ -1341,7 +1341,7 @@ export function searchChannelsInPolicy(state: GlobalState, policyId: string, ter
export function getDirectTeammate(state: GlobalState, channelId: string): UserProfile | undefined {
const channel = getChannel(state, channelId);
if (!channel) {
if (!channel || channel.type !== 'D') {
return undefined;
}
@@ -1444,3 +1444,8 @@ export const getRecentProfilesFromDMs: (state: GlobalState) => UserProfile[] = c
return [...sortedUserProfiles];
},
);
export const isDeactivatedDirectChannel = (state: GlobalState, channelId: string) => {
const teammate = getDirectTeammate(state, channelId);
return Boolean(teammate && teammate.delete_at);
};