-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Scheduled post indicator #8522
Scheduled post indicator #8522
Changes from all commits
23adb2c
e2661a4
62738c2
e8eafa9
09c5cc2
a74d1c3
821aa3f
9acdb90
c470968
de651db
f093a60
d3ef484
7c3d9f5
0d319b3
079382b
152bcf5
94c6300
b061ccd
25a19c7
c0b0692
c7e9132
36f4861
2c8179c
7ffd151
7ad4983
90fc974
9f892ca
248c2fd
8937f57
a01b17e
d24c828
44ac162
ba9053d
4aae8d5
dc2e865
32777f2
1015461
78d5f45
f49ab74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||
// See LICENSE.txt for license information. | ||
|
||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react'; | ||
import {map} from 'rxjs/operators'; | ||
|
||
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference'; | ||
import {queryDisplayNamePreferences} from '@queries/servers/preference'; | ||
import {observeCurrentUser} from '@queries/servers/user'; | ||
|
||
import {ScheduledPostIndicator} from './scheduled_post_indicator'; | ||
|
||
const enhance = withObservables([], ({database}) => { | ||
const currentUser = observeCurrentUser(database); | ||
const preferences = queryDisplayNamePreferences(database). | ||
observeWithColumns(['value']); | ||
const isMilitaryTime = preferences.pipe(map((prefs) => getDisplayNamePreferenceAsBool(prefs, 'use_military_time'))); | ||
|
||
return { | ||
currentUser, | ||
isMilitaryTime, | ||
}; | ||
}); | ||
|
||
export default withDatabase(enhance(ScheduledPostIndicator)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||
// See LICENSE.txt for license information. | ||
|
||
import {Database} from '@nozbe/watermelondb'; | ||
import {screen} from '@testing-library/react-native'; | ||
import React from 'react'; | ||
|
||
import NetworkManager from '@managers/network_manager'; | ||
import {renderWithEverything} from '@test/intl-test-helper'; | ||
import TestHelper from '@test/test_helper'; | ||
|
||
import ScheduledPostIndicator from './'; | ||
|
||
import type ServerDataOperator from '@database/operator/server_data_operator'; | ||
|
||
const SERVER_URL = 'https://appv1.mattermost.com'; | ||
|
||
// this is needed when using the useServerUrl hook | ||
jest.mock('@context/server', () => ({ | ||
useServerUrl: jest.fn(() => SERVER_URL), | ||
})); | ||
|
||
describe('components/scheduled_post_indicator', () => { | ||
let database: Database; | ||
let operator: ServerDataOperator; | ||
|
||
beforeEach(async () => { | ||
const server = await TestHelper.setupServerDatabase(SERVER_URL); | ||
database = server.database; | ||
operator = server.operator; | ||
}); | ||
|
||
afterEach(async () => { | ||
await TestHelper.tearDown(); | ||
NetworkManager.invalidateClient(SERVER_URL); | ||
}); | ||
|
||
it('should render single scheduled post indicator correctly', async () => { | ||
const {getByText} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
isThread={false} | ||
scheduledPostCount={1} | ||
/>, | ||
{database}, | ||
); | ||
|
||
await screen.findByTestId('scheduled_post_indicator_single_time'); | ||
expect(getByText(/Message scheduled for/)).toBeVisible(); | ||
expect(getByText(/See all./)).toBeVisible(); | ||
}); | ||
|
||
it('should render multiple scheduled posts indicator for channel', async () => { | ||
const {getByText} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
isThread={false} | ||
scheduledPostCount={10} | ||
/>, | ||
{database}, | ||
); | ||
|
||
expect(getByText(/10 scheduled messages in channel./)).toBeVisible(); | ||
expect(getByText(/See all./)).toBeVisible(); | ||
}); | ||
|
||
it('should render multiple scheduled posts indicator for thread', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. duplicate with above? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope, above one is for channels and this one is for threads. The text changes based on |
||
const {getByText} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
isThread={true} | ||
scheduledPostCount={10} | ||
/>, | ||
{database}, | ||
); | ||
|
||
expect(getByText(/10 scheduled messages in thread./)).toBeVisible(); | ||
expect(getByText(/See all./)).toBeVisible(); | ||
}); | ||
|
||
it('renders with military time when preference is set', async () => { | ||
await operator.handlePreferences({ | ||
preferences: [ | ||
{ | ||
user_id: 'user_1', | ||
category: 'display_settings', | ||
name: 'use_military_time', | ||
value: 'true', | ||
}, | ||
], | ||
prepareRecordsOnly: false, | ||
}); | ||
|
||
const {getByText, findByTestId} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
scheduledPostCount={1} | ||
/>, | ||
{database}, | ||
); | ||
|
||
const timeElement = await findByTestId('scheduled_post_indicator_single_time'); | ||
expect(timeElement).toBeVisible(); | ||
|
||
expect(getByText(/19:41/)).toBeVisible(); | ||
}); | ||
|
||
it('renders with 12-hour time when preference is set to 12 hours', async () => { | ||
await operator.handlePreferences({ | ||
preferences: [ | ||
{ | ||
user_id: 'user_1', | ||
category: 'display_settings', | ||
name: 'use_military_time', | ||
value: 'false', | ||
}, | ||
], | ||
prepareRecordsOnly: false, | ||
}); | ||
|
||
const {getByText, findByTestId} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
scheduledPostCount={1} | ||
/>, | ||
{database}, | ||
); | ||
|
||
const timeElement = await findByTestId('scheduled_post_indicator_single_time'); | ||
expect(timeElement).toBeVisible(); | ||
|
||
expect(getByText(/7:41 PM/)).toBeVisible(); | ||
}); | ||
|
||
it('renders with 12-hour time when preference is not set', async () => { | ||
const {getByText, findByTestId} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
scheduledPostCount={1} | ||
/>, | ||
{database}, | ||
); | ||
|
||
const timeElement = await findByTestId('scheduled_post_indicator_single_time'); | ||
expect(timeElement).toBeVisible(); | ||
|
||
expect(getByText(/7:41 PM/)).toBeVisible(); | ||
}); | ||
|
||
it('handles missing current user', async () => { | ||
const {getByText, findByTestId} = renderWithEverything( | ||
<ScheduledPostIndicator | ||
scheduledPostCount={1} | ||
/>, | ||
{database}, | ||
); | ||
|
||
const timeElement = await findByTestId('scheduled_post_indicator_single_time'); | ||
expect(timeElement).toBeVisible(); | ||
expect(getByText(/Message scheduled for/)).toBeVisible(); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a couple of prop that can be undefined that you are not testing: scheduledPostCount and isThread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't forget unit test for this component. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After feature complete. Please be rest assured we will add the tests. |
||
// See LICENSE.txt for license information. | ||
|
||
import React from 'react'; | ||
import {FormattedMessage} from 'react-intl'; | ||
import {Text, View} from 'react-native'; | ||
|
||
import CompassIcon from '@components/compass_icon'; | ||
import FormattedTime from '@components/formatted_time'; | ||
import {useTheme} from '@context/theme'; | ||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; | ||
import {getUserTimezone} from '@utils/user'; | ||
|
||
import type UserModel from '@typings/database/models/servers/user'; | ||
|
||
const WRAPPER_HEIGHT = 40; | ||
|
||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { | ||
return { | ||
wrapper: { | ||
height: WRAPPER_HEIGHT, | ||
}, | ||
container: { | ||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08), | ||
paddingVertical: 12, | ||
paddingBottom: 20, | ||
paddingHorizontal: 16, | ||
color: changeOpacity(theme.centerChannelColor, 0.72), | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
gap: 12, | ||
position: 'absolute', | ||
top: 0, | ||
width: '100%', | ||
}, | ||
text: { | ||
color: changeOpacity(theme.centerChannelColor, 0.75), | ||
fontSize: 14, | ||
}, | ||
link: { | ||
color: theme.linkColor, | ||
fontSize: 14, | ||
}, | ||
}; | ||
}); | ||
|
||
type Props = { | ||
currentUser?: UserModel; | ||
isMilitaryTime: boolean; | ||
isThread?: boolean; | ||
scheduledPostCount?: number; | ||
} | ||
|
||
export function ScheduledPostIndicator({currentUser, isMilitaryTime, isThread, scheduledPostCount = 0}: Props) { | ||
const theme = useTheme(); | ||
const styles = getStyleSheet(theme); | ||
|
||
let scheduledPostText: React.ReactNode; | ||
|
||
if (scheduledPostCount === 0) { | ||
return null; | ||
} else if (scheduledPostCount === 1) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if it's more than 1? You will not show dateTime at all? Why not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, because we cannot show date and time of all scheduled posts in a small indicator in channel. See the attached screenshot in PR description for example. |
||
// eslint-disable-next-line no-warning-comments | ||
//TODO: remove this hardcoded value with actual value | ||
const value = 1738611689000; | ||
|
||
const dateTime = ( | ||
<FormattedTime | ||
timezone={getUserTimezone(currentUser)} | ||
isMilitaryTime={isMilitaryTime} | ||
value={value} | ||
testID='scheduled_post_indicator_single_time' | ||
/> | ||
); | ||
|
||
scheduledPostText = ( | ||
<FormattedMessage | ||
id='scheduled_post.channel_indicator.single' | ||
defaultMessage='Message scheduled for {dateTime}.' | ||
values={{ | ||
dateTime, | ||
}} | ||
/> | ||
); | ||
} else { | ||
scheduledPostText = isThread ? ( | ||
<FormattedMessage | ||
id='scheduled_post.channel_indicator.thread.multiple' | ||
defaultMessage='{count} scheduled messages in thread.' | ||
values={{ | ||
count: scheduledPostCount, | ||
}} | ||
/> | ||
) : ( | ||
<FormattedMessage | ||
id='scheduled_post.channel_indicator.multiple' | ||
defaultMessage='{count} scheduled messages in channel.' | ||
values={{ | ||
count: scheduledPostCount, | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<View | ||
className='ScheduledPostIndicator' | ||
style={styles.wrapper} | ||
> | ||
<View style={styles.container}> | ||
<CompassIcon | ||
color={changeOpacity(theme.centerChannelColor, 0.6)} | ||
name='clock-send-outline' | ||
size={18} | ||
/> | ||
<Text style={styles.text}> | ||
{scheduledPostText} | ||
{' '} | ||
<Text style={styles.link}> | ||
<FormattedMessage | ||
id='scheduled_post.channel_indicator.link_to_scheduled_posts.text' | ||
defaultMessage='See all.' | ||
/> | ||
</Text> | ||
Comment on lines
+120
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the plan here? For this to be a touchable, or for the whole banner to be a touchable? We are not yet implementing this part because we don't have the screen, right? Or am I missing something? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. User will be able to click here to go the screen, which doesn't exist yet. hence, only text for now. |
||
</Text> | ||
</View> | ||
</View> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -989,6 +989,10 @@ | |
"saved_messages.empty.title": "No saved messages yet", | ||
"scheduled_post_options.confirm_button.processing.text": "Scheduling", | ||
"scheduled_post_options.confirm_button.text": "Schedule Draft", | ||
"scheduled_post.channel_indicator.link_to_scheduled_posts.text": "See all.", | ||
"scheduled_post.channel_indicator.multiple": "{count} scheduled messages in channel.", | ||
"scheduled_post.channel_indicator.single": "Message scheduled for {dateTime}.", | ||
Comment on lines
+992
to
+994
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a lot of messages ending in a full stop, such as |
||
"scheduled_post.channel_indicator.thread.multiple": "{count} scheduled messages in thread.", | ||
"scheduled_post.picker.custom": "Custom Time", | ||
"scheduled_post.picker.monday": "Monday at {9amTime}", | ||
"scheduled_post.picker.next_monday": "Next Monday at {9amTime}", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes here seem to affect no matter if the scheduled post message is visible or not. Is that intended? Is there any visual change due to this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks right both with and without the scheduled post indicator with the same change.
Here is a video of with and without scheduled post indicator. Maybe I'll need to add some extra spacing conditionally, but will figure it out later.
Screen.Recording.2025-02-04.at.4.42.06.PM.mov
Screen.Recording.2025-02-04.at.4.40.44.PM.mov