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

Create scheduled post api integration #8519

Merged
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
b7d0ba6
WIP
harshilsharma63 Jan 16, 2025
cb9a62b
Separate component for core and custom options
harshilsharma63 Jan 17, 2025
b2b144d
WIP
harshilsharma63 Jan 17, 2025
7a13704
WIP
harshilsharma63 Jan 17, 2025
52bfcdd
WIP
harshilsharma63 Jan 17, 2025
08c9a53
Merge branch 'feature_schedule_posts' into show_scheduled_post_options
harshilsharma63 Jan 20, 2025
5ea68e1
unified options into single component
harshilsharma63 Jan 20, 2025
2a2d101
Scheduled post menu ready
harshilsharma63 Jan 20, 2025
37aa32b
Merge branch 'feature_schedule_posts' into show_scheduled_post_options
harshilsharma63 Jan 21, 2025
d62bb3c
Displayed date time picker by default
harshilsharma63 Jan 21, 2025
4be9065
WIP
harshilsharma63 Jan 22, 2025
96dcab2
Lint fix
harshilsharma63 Jan 22, 2025
7dc66b5
i18n fix
harshilsharma63 Jan 22, 2025
27344f1
removed unused screen
harshilsharma63 Jan 22, 2025
34f7ced
API call sent successfully
harshilsharma63 Jan 22, 2025
e5527d0
Merge branch 'feature_schedule_posts' into show_scheduled_post_options
harshilsharma63 Jan 23, 2025
4e893a1
Added user time format preference
harshilsharma63 Jan 23, 2025
d34305d
review fixex and improvements
harshilsharma63 Jan 23, 2025
d6ab9d1
Merge branch 'show_scheduled_post_options' into create_scheduled_post…
harshilsharma63 Jan 23, 2025
2edf74a
Handled loading sttae
harshilsharma63 Jan 23, 2025
976af46
Handled loading sttae
harshilsharma63 Jan 23, 2025
e832f05
Handled mentions and files and priority
harshilsharma63 Jan 24, 2025
82c8fb0
Cleanup
harshilsharma63 Jan 24, 2025
f08a7e6
lint fix
harshilsharma63 Jan 24, 2025
1498cb9
i18n fix
harshilsharma63 Jan 24, 2025
8a63e96
WIP
harshilsharma63 Jan 24, 2025
d443026
Displayed error snackbar
harshilsharma63 Jan 27, 2025
5ade7cd
Fixed time used for debugging
harshilsharma63 Jan 27, 2025
bfaf507
Lint fix
harshilsharma63 Jan 27, 2025
06dbcdf
Merge branch 'feature_schedule_posts' into show_scheduled_post_options
harshilsharma63 Jan 27, 2025
bdc6113
Merge branch 'show_scheduled_post_options' into create_scheduled_post…
harshilsharma63 Jan 27, 2025
cd62280
Merge branch 'feature_schedule_posts' into show_scheduled_post_options
harshilsharma63 Jan 28, 2025
7699011
Merge branch 'show_scheduled_post_options' into create_scheduled_post…
harshilsharma63 Jan 28, 2025
69efb06
review fixex and improvements
harshilsharma63 Jan 28, 2025
5141c7d
Merge branch 'feature_schedule_posts' into show_scheduled_post_options
harshilsharma63 Jan 29, 2025
c06a248
Renamed a file
harshilsharma63 Jan 29, 2025
7ad7a15
Merge branch 'show_scheduled_post_options' into create_scheduled_post…
harshilsharma63 Jan 29, 2025
637d5d0
Adding tests
harshilsharma63 Jan 29, 2025
2d6eb3b
Adding tests
harshilsharma63 Jan 29, 2025
4c1e54f
lint fix
harshilsharma63 Jan 29, 2025
90b28aa
Merge branch 'feature_schedule_posts' into create_scheduled_post_api_…
harshilsharma63 Jan 29, 2025
40c3f8d
cleanup
harshilsharma63 Jan 29, 2025
b57c3c3
Merge branch 'feature_schedule_posts' into create_scheduled_post_api_…
harshilsharma63 Jan 30, 2025
dd5bb8c
Added draft_input tests
harshilsharma63 Jan 30, 2025
f3a09f0
test: Add comprehensive tests for SendAction component
harshilsharma63 Jan 30, 2025
d322d8d
Added send_action tests
harshilsharma63 Jan 30, 2025
e8d9f46
lint fix
harshilsharma63 Jan 30, 2025
0c5cd53
test: Add comprehensive test coverage for useHandleSendMessage hook
harshilsharma63 Jan 30, 2025
a69e820
test: Add test for scheduled post creation in send message hook
harshilsharma63 Jan 30, 2025
08d125a
fix: Mock createScheduledPost function in test to resolve matcher error
harshilsharma63 Jan 30, 2025
16158b4
WIP tests
harshilsharma63 Jan 31, 2025
48db248
test: Add tests for SnackBar component
harshilsharma63 Jan 31, 2025
7350939
Added snack bar tests
harshilsharma63 Jan 31, 2025
fe617c9
Cleanup
harshilsharma63 Jan 31, 2025
8ebfd42
test: Add empty test file for scheduled post picker
harshilsharma63 Jan 31, 2025
2f91aa9
test: Add tests for ScheduledPostOptions component
harshilsharma63 Jan 31, 2025
20a1dd8
test: Improve ScheduledPostOptions test coverage and error handling
harshilsharma63 Jan 31, 2025
c3bb8a4
test: Update ScheduledPostOptions test suite with new database and ti…
harshilsharma63 Jan 31, 2025
2edc2b8
test: Fix timeout issues in ScheduledPostOptions test suite
harshilsharma63 Jan 31, 2025
d280d27
Added scheuidled post picker tests
harshilsharma63 Jan 31, 2025
ed7c9ad
test: Add empty test file for scheduled post footer
harshilsharma63 Jan 31, 2025
37a0acc
test: Add comprehensive tests for ScheduledPostFooter component
harshilsharma63 Jan 31, 2025
7ba4d9c
feat: Add animatedFooterPosition prop to ScheduledPostFooter test
harshilsharma63 Jan 31, 2025
2e4e447
test: Add tablet rendering test for ScheduledPostFooter
harshilsharma63 Jan 31, 2025
8595da3
test: Update ScheduledPostFooter test mocks and remove unused imports
harshilsharma63 Jan 31, 2025
688ef6d
test: Add mocks for BottomSheet context in scheduled post footer tests
harshilsharma63 Jan 31, 2025
d64f8e0
Added scheudled post footer test
harshilsharma63 Jan 31, 2025
eac1e87
test: Add comprehensive tests for handle_send_message hook
harshilsharma63 Jan 31, 2025
35e7460
Enhanced handle send message tests
harshilsharma63 Jan 31, 2025
e1fbd58
cleanup
harshilsharma63 Jan 31, 2025
bbf2cba
Added missing snapshots
harshilsharma63 Jan 31, 2025
be5ba6b
Merge branch 'feature_schedule_posts' into create_scheduled_post_api_…
harshilsharma63 Jan 31, 2025
a907328
test: Add error logging and force logout validation for scheduled pos…
harshilsharma63 Feb 3, 2025
b34fef9
Updated test
harshilsharma63 Feb 3, 2025
67c03c1
used toBeVisible instead of toBeTruthy
harshilsharma63 Feb 3, 2025
2c89ccb
test: Update test assertions to use toHaveBeenCalledWith
harshilsharma63 Feb 3, 2025
8086888
test: Add assertions to prevent sendMessage when send button is disabled
harshilsharma63 Feb 3, 2025
db90702
test: Replace snapshot tests with explicit UI element checks
harshilsharma63 Feb 3, 2025
df72fba
test improvements
harshilsharma63 Feb 3, 2025
d4cf535
Deleted unused snapshots
harshilsharma63 Feb 3, 2025
b940f71
Updated snaopshot
harshilsharma63 Feb 3, 2025
5a2cf9c
feat: Add close button ID for scheduled post options bottom sheet
harshilsharma63 Feb 3, 2025
9da20ba
test: Add mock scheduling flow to scheduled post options test
harshilsharma63 Feb 3, 2025
5de1382
Fixed incorrect test
harshilsharma63 Feb 3, 2025
fccde48
scheudled post test enhancement
harshilsharma63 Feb 3, 2025
a9ffd89
minimised a test
harshilsharma63 Feb 3, 2025
cb2e36e
Scheduled post indicator (#8522)
harshilsharma63 Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions app/actions/remote/scheduled_post.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';

import {createScheduledPost} from './scheduled_post';

import type ServerDataOperator from '@database/operator/server_data_operator';

const serverUrl = 'baseHandler.test.com';
let operator: ServerDataOperator;

const user1 = {id: 'userid1', username: 'user1', email: '[email protected]', roles: ''} as UserProfile;
const scheduledPost = {
id: 'scheduledPostId1',
root_id: '',
update_at: 0,
channel_id: 'channelid1',
message: 'Test message',
scheduled_at: Date.now() + 10000,
} as ScheduledPost;

const throwFunc = () => {
throw Error('error');
};

const mockClient = {
createScheduledPost: jest.fn(() => ({...scheduledPost})),
};

beforeAll(() => {
// eslint-disable-next-line
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need both?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, removed and used ts-expect-error, otherwise we'd need both.

NetworkManager.getClient = () => mockClient;
});

beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
});

afterEach(async () => {
await DatabaseManager.destroyServerDatabase(serverUrl);
});

describe('scheduled_post', () => {
it('createScheduledPost - handle not found database', async () => {
const result = await createScheduledPost('foo', scheduledPost);
expect(result).toBeDefined();
expect(result.error).toBeDefined();
Copy link
Contributor

Choose a reason for hiding this comment

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

Kinda redundant. If you have result.error, you know result is defined.

But it would be more beneficial to know the error is what you expect. Like 'database not found' or something.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

});

it('createScheduledPost - base case', async () => {
await operator.handleUsers({users: [user1], prepareRecordsOnly: false});
const result = await createScheduledPost(serverUrl, scheduledPost);
expect(result).toBeDefined();
expect(result.error).toBeUndefined();
expect(result.data).toBe(true);
expect(result.response).toBeDefined();
if (result.response) {
expect(result.response.id).toBe(scheduledPost.id);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above, I think you can skip result toBeDefined().

The check for result.response is unnecessary if you have already expect result.response toBeDefined() right?

I barely see any need for if-else statements in test. You're not creating conditions in your test, you're trying to paint a story of what you're expecting.

In this instance:

expect(result.response.id).toBe(scheduledPost.id);

negates the need to check if it's defined, or that result.response is true/exists.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.


it('createScheduledPost - request error', async () => {
mockClient.createScheduledPost.mockImplementationOnce(jest.fn(throwFunc));
const result = await createScheduledPost(serverUrl, scheduledPost);
expect(result).toBeDefined();
expect(result.error).toBeDefined();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: We could check that on error, we are also logging the error and calling logout if necessary.

We would just have to mock the logDebug and the forceLogoutIfNecessary and making sure they are called.

You could also make sure the error logged / returned is the one returned by the client function.

0/5 if we have to be so meticulous though

Copy link
Contributor

Choose a reason for hiding this comment

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

I like it meticulous. Say somebody remove the forceLogoutIfNecessary by accident, that meticulousness will flag and bring a discussion if that removal is necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

});
});
24 changes: 13 additions & 11 deletions app/actions/remote/scheduled_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,28 @@
// See LICENSE.txt for license information.

import {forceLogoutIfNecessary} from '@actions/remote/session';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
import websocketManager from '@managers/websocket_manager';
import {getFullErrorMessage} from '@utils/errors';
import {logError} from '@utils/log';

export async function createScheduledPost(serverUrl: string, schedulePost: ScheduledPost, connectionId?: string, fetchOnly = false) {
import type {CreateResponse} from '@hooks/handle_send_message';

export async function createScheduledPost(serverUrl: string, schedulePost: ScheduledPost, connectionId?: string): Promise<CreateResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

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

(Not a request for changes, just a knowledge share)
In general for remote actions we add another optional parameter fetchOnly, default to false. This is so we can decide not to update the local state when we get certain information. This may be done for several reasons, like fetching data that is not going to be kept up to date (nor by websocket nor syncing logic), so we don't want it in the database. This could be, for example, browse channels results.

In this particular case, I don't foresee any case where we want to create a schedule post and not update the local database, so it should be just fine not having the parameter.

Copy link
Member Author

Choose a reason for hiding this comment

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

I had fetchOnly but since this doesn't make aAPI calls, the linter complained about an ununsed param and I removed it. I will add it when needed.

const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}

try {
const client = NetworkManager.getClient(serverUrl);
const ws = websocketManager.getClient(serverUrl);

const created = await client.createScheduledPost(schedulePost, ws?.getConnectionId());
const response = await client.createScheduledPost(schedulePost, connectionId);

if (!fetchOnly) {
// TODO - to be implemented later once DB tables are ready
// await operator.handleScheduledPost({scheduledPosts: [created], prepareRecordsOnly: false});
}
return {scheduledPost: created};
// TODO - record scheduled post in database here
return {data: true, response};
Copy link
Contributor

Choose a reason for hiding this comment

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

I still think that data should be the response directly, not a boolean. See for example fetchThread.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would tend to agree with you @larkox, but apparently this is an else for createPost(), which also returns {data: true/false}.

Copy link
Member Author

Choose a reason for hiding this comment

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

It has to be data: boolean and response separately as doSubmitMessage call either createPost or createScheduledPost and both need to return the same structure. Create Post returns data: boolean, and so for that reason, so does createScheduledPost have to and updating createPost is beyond the scope of this feature.

} catch (error) {
logError('error on createScheduledPost', getFullErrorMessage(error));
forceLogoutIfNecessary(serverUrl, error);
return {error};
return {error: getFullErrorMessage(error)};
}
}
18 changes: 11 additions & 7 deletions app/components/post_draft/draft_input/draft_input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Props = {
cursorPosition: number;

// Send Handler
sendMessage: () => void;
sendMessage: (schedulingInfo?: SchedulingInfo) => Promise<void | {data?: boolean; error?: unknown}>;
canSend: boolean;
maxMessageLength: number;

Expand Down Expand Up @@ -164,13 +164,16 @@ function DraftInput({
postPriority,
});

const handleSendMessage = useCallback(async () => {
const handleSendMessage = useCallback(async (schedulingInfoParam?: SchedulingInfo) => {
const schedulingInfo = (schedulingInfoParam && 'scheduled_at' in schedulingInfoParam) ? schedulingInfoParam : undefined;

if (persistentNotificationsEnabled) {
persistentNotificationsConfirmation(serverUrl, value, mentionsList, intl, sendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType);
} else {
sendMessage();
// const sendMessageWithScheduledPost = () => sendMessage(schedulingInfo);
await persistentNotificationsConfirmation(serverUrl, value, mentionsList, intl, sendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType);
return Promise.resolve();
}
}, [serverUrl, mentionsList, persistentNotificationsEnabled, persistentNotificationMaxRecipients, sendMessage, value, channelType]);
return sendMessage(schedulingInfo);
}, [persistentNotificationsEnabled, serverUrl, value, mentionsList, intl, sendMessage, persistentNotificationMaxRecipients, persistentNotificationInterval, currentUserId, channelName, channelType]);

const handleShowScheduledPostOptions = useCallback(() => {
if (!scheduledPostsEnabled) {
Expand All @@ -187,9 +190,10 @@ function DraftInput({
title,
props: {
closeButtonId: SCHEDULED_POST_PICKER_BUTTON,
onSchedule: handleSendMessage,
},
});
}, [intl, isTablet, scheduledPostsEnabled, theme]);
}, [handleSendMessage, intl, isTablet, scheduledPostsEnabled, theme]);

const sendActionDisabled = !canSend || noMentionsError;

Expand Down
5 changes: 4 additions & 1 deletion app/components/post_draft/send_action/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useTheme} from '@context/theme';
import {usePreventDoubleTap} from '@hooks/utils';
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment is for this entire file. And I'm still iffy on the standard.

What I understand is if it's an index.tsx file, then this is an observable file. Like database observable file.

If it's a presentation file, it would be named send_action.tsx.

  1. If the file is for send_action, why not call the component SendAction? It it's SendButton component, why not make the file name send_button.tsx?

When I search for SendAction, I was expecting this particular file to be showing in my search result, to make developer's experience a bit easier.

I'm still new with the mobile repo, and the conventions, but we need to establish it properly.

Open for discussion @larkox .

Copy link
Member Author

Choose a reason for hiding this comment

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

Its an existing where I made a two line changes. The issues described above are existing, not new. A separate ticket can be filed for these unrelated changes.

import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';

type Props = {
Expand Down Expand Up @@ -55,10 +56,12 @@ function SendButton({

const buttonColor = disabled ? changeOpacity(theme.buttonColor, 0.5) : theme.buttonColor;

const sendMessageWithDoubleTapPrevention = usePreventDoubleTap(sendMessage);

return (
<TouchableWithFeedback
testID={sendButtonTestID}
onPress={sendMessage}
onPress={sendMessageWithDoubleTapPrevention}
style={style.sendButtonContainer}
type={'opacity'}
disabled={disabled}
Expand Down
3 changes: 2 additions & 1 deletion app/constants/snack_bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const SNACK_BAR_TYPE = keyMirror({
UNFAVORITE_CHANNEL: null,
UNMUTE_CHANNEL: null,
UNFOLLOW_THREAD: null,
SCHEDULED_POST_CREATION_ERROR: null,
});

export const MESSAGE_TYPE = {
Expand All @@ -27,7 +28,7 @@ export const MESSAGE_TYPE = {
DEFAULT: 'default',
};

type SnackBarConfig = {
export type SnackBarConfig = {
rahimrahman marked this conversation as resolved.
Show resolved Hide resolved
id: string;
defaultMessage: string;
iconName: string;
Expand Down
16 changes: 0 additions & 16 deletions app/hooks/handle_send_message.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,22 +141,6 @@ describe('useHandleSendMessage', () => {
expect(DeviceEventEmitter.emit).toHaveBeenCalledWith(Events.POST_LIST_SCROLL_TO_BOTTOM, Screens.THREAD);
});

it('should not allow sending while already sending', async () => {
const {result} = renderHook(() => useHandleSendMessage(defaultProps), {wrapper});

// Trigger first send
await act(async () => {
result.current.handleSendMessage();
});

// Try to send again immediately
await act(async () => {
result.current.handleSendMessage();
});

expect(createPost).toHaveBeenCalledTimes(1);
});

it('should not allow sending with uploading files', () => {
const props = {
...defaultProps,
Expand Down
53 changes: 38 additions & 15 deletions app/hooks/handle_send_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {getChannelTimezones} from '@actions/remote/channel';
import {executeCommand, handleGotoLocation} from '@actions/remote/command';
import {createPost} from '@actions/remote/post';
import {handleReactionToLatestPost} from '@actions/remote/reactions';
import {createScheduledPost} from '@actions/remote/scheduled_post';
import {setStatus} from '@actions/remote/user';
import {handleCallsSlashCommand} from '@calls/actions';
import {Events, Screens} from '@constants';
Expand All @@ -18,11 +19,17 @@ import DraftUploadManager from '@managers/draft_upload_manager';
import * as DraftUtils from '@utils/draft';
import {isReactionMatch} from '@utils/emoji/helpers';
import {getFullErrorMessage} from '@utils/errors';
import {preventDoubleTap} from '@utils/tap';
import {scheduledPostFromPost} from '@utils/post';
import {confirmOutOfOfficeDisabled} from '@utils/user';

import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';

export type CreateResponse = {
data?: boolean;
error?: unknown;
response?: Post | ScheduledPost;
}

type Props = {
value: string;
channelId: string;
Expand Down Expand Up @@ -86,7 +93,7 @@ export const useHandleSendMessage = ({
setSendingMessage(false);
}, [serverUrl, rootId, clearDraft]);

const doSubmitMessage = useCallback(() => {
const doSubmitMessage = useCallback(async (schedulingInfo?: SchedulingInfo): Promise<CreateResponse> => {
const postFiles = files.filter((f) => !f.failed);
const post = {
user_id: currentUserId,
Expand All @@ -105,20 +112,31 @@ export const useHandleSendMessage = ({
};
}

createPost(serverUrl, post, postFiles);
let response: CreateResponse;
if (schedulingInfo) {
response = await createScheduledPost(serverUrl, scheduledPostFromPost(post, schedulingInfo, postPriority));
} else {
response = await createPost(serverUrl, post, postFiles);
}

clearDraft();
setSendingMessage(false);
DeviceEventEmitter.emit(Events.POST_LIST_SCROLL_TO_BOTTOM, rootId ? Screens.THREAD : Screens.CHANNEL);

return response;
}, [files, currentUserId, channelId, rootId, value, postPriority, serverUrl, clearDraft]);

const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean) => {
const showSendToAllOrChannelOrHereAlert = useCallback((calculatedMembersCount: number, atHere: boolean, schedulingInfo?: SchedulingInfo) => {
const notifyAllMessage = DraftUtils.buildChannelWideMentionMessage(intl, calculatedMembersCount, channelTimezoneCount, atHere);
const cancel = () => {
setSendingMessage(false);
};

DraftUtils.alertChannelWideMention(intl, notifyAllMessage, doSubmitMessage, cancel);
// Creating a wrapper function to pass the schedulingInfo to the doSubmitMessage function as the accepted
// function signature causes conflict.\
// TODO for later - change alert message if this is a scheduled post
const doSubmitMessageScheduledPostWrapper = () => doSubmitMessage(schedulingInfo);
DraftUtils.alertChannelWideMention(intl, notifyAllMessage, doSubmitMessageScheduledPostWrapper, cancel);
}, [intl, channelTimezoneCount, doSubmitMessage]);

const sendCommand = useCallback(async () => {
Expand Down Expand Up @@ -167,31 +185,34 @@ export const useHandleSendMessage = ({
}
}, [value, userIsOutOfOffice, serverUrl, intl, channelId, rootId, clearDraft, channelType, currentUserId]);

const sendMessage = useCallback(() => {
const sendMessage = useCallback(async (schedulingInfo?: SchedulingInfo) => {
const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions;
const toAllOrChannel = DraftUtils.textContainsAtAllAtChannel(value);
const toHere = DraftUtils.textContainsAtHere(value);

if (value.indexOf('/') === 0) {
if (value.indexOf('/') === 0 && !schedulingInfo) {
// Don't execute slash command when scheduling message
sendCommand();
} else if (notificationsToChannel && membersCount > NOTIFY_ALL_MEMBERS && (toAllOrChannel || toHere)) {
showSendToAllOrChannelOrHereAlert(membersCount, toHere && !toAllOrChannel);
showSendToAllOrChannelOrHereAlert(membersCount, toHere && !toAllOrChannel, schedulingInfo);
} else {
doSubmitMessage();
return doSubmitMessage(schedulingInfo);
}

return Promise.resolve();
}, [enableConfirmNotificationsToChannel, useChannelMentions, value, membersCount, sendCommand, showSendToAllOrChannelOrHereAlert, doSubmitMessage]);

const handleSendMessage = useCallback(preventDoubleTap(() => {
const handleSendMessage = useCallback(async (schedulingInfo?: SchedulingInfo) => {
if (!canSend) {
return;
return Promise.resolve();
}

setSendingMessage(true);

const match = isReactionMatch(value, customEmojis);
if (match && !files.length) {
handleReaction(match.emoji, match.add);
return;
return Promise.resolve();
}

const hasFailedAttachments = files.some((f) => f.failed);
Expand All @@ -201,14 +222,16 @@ export const useHandleSendMessage = ({
};
const accept = () => {
// Files are filtered on doSubmitMessage
sendMessage();
sendMessage(schedulingInfo);
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably want to do things (among others, wait for sendMessage when we accept, right? Or is this flow completely different?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm unable to test this out right now, but I've filed a ticket (https://mattermost.atlassian.net/browse/MM-62756) for handle this.

};

DraftUtils.alertAttachmentFail(intl, accept, cancel);
} else {
sendMessage();
return sendMessage(schedulingInfo);
}
}), [canSend, value, handleReaction, files, sendMessage, customEmojis]);

return Promise.resolve();
}, [canSend, value, customEmojis, files, handleReaction, intl, sendMessage]);

useEffect(() => {
getChannelTimezones(serverUrl, channelId).then(({channelTimezones}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const ConvertGMToChannelForm = ({
const defaultUserDisplayNames = formatMessage({id: 'channel_info.convert_gm_to_channel.warning.body.yourself', defaultMessage: 'yourself'});
const memberNames = profiles.length > 0 ? formatList(userDisplayNames) : defaultUserDisplayNames;
const messageBoxBody = formatMessage({
id: 'channel_info.convert_gm_to_channel.warning.bodyXXXX',
id: 'channel_info.convert_gm_to_channel.warning.body',
defaultMessage: 'You are about to convert the Group Message with {memberNames} to a Channel. This cannot be undone.',
}, {
memberNames,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ export function ScheduledPostCoreOptions({userTimezone, isMilitaryTime, onSelect
}

if (selectedTime) {
onSelectOption(selectedTime.unix().toString());
onSelectOption(selectedTime.valueOf().toString());
}
}, [now, onSelectOption]);

const handleCustomTimeChange = useCallback((selectedTime: Moment) => {
onSelectOption(selectedTime.unix().toString());
onSelectOption(selectedTime.valueOf().toString());
}, [onSelectOption]);

const nineAmTime = moment().
Expand Down
Loading
Loading