MM-55251 - Updating reply count on thread post (#26142)

* MM-55251 - Updating reply count on thread post

* Updating reply count fetch

* Fixing lint errors

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Asaad Mahmood 2024-03-12 14:24:47 +05:00 committed by GitHub
parent 2cc2bad1b3
commit 76bab25199
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 70 additions and 15 deletions

View File

@ -60,6 +60,7 @@ function ThreadFooter({
channel_id: channelId, channel_id: channelId,
}, },
} = thread; } = thread;
const participantIds = useMemo(() => (participants || []).map(({id}) => id).reverse(), [participants]); const participantIds = useMemo(() => (participants || []).map(({id}) => id).reverse(), [participants]);
const handleReply = useCallback((e) => { const handleReply = useCallback((e) => {

View File

@ -32,6 +32,7 @@ describe('components/threading/ThreadViewer', () => {
user_id: post.user_id, user_id: post.user_id,
channel_id: post.channel_id, channel_id: post.channel_id,
message: post.message, message: post.message,
reply_count: 3,
}; };
const channel: Channel = TestHelper.getChannelMock({ const channel: Channel = TestHelper.getChannelMock({

View File

@ -22,6 +22,7 @@ import Reply from './reply';
type Props = { type Props = {
a11yIndex: number; a11yIndex: number;
currentUserId: string; currentUserId: string;
replyCount: number;
isRootPost: boolean; isRootPost: boolean;
isLastPost: boolean; isLastPost: boolean;
listId: string; listId: string;
@ -40,6 +41,7 @@ function ThreadViewerRow({
isRootPost, isRootPost,
isLastPost, isLastPost,
listId, listId,
replyCount,
onCardClick, onCardClick,
previousPostId, previousPostId,
timestampProps, timestampProps,
@ -70,6 +72,7 @@ function ThreadViewerRow({
case isRootPost: case isRootPost:
return ( return (
<>
<PostComponent <PostComponent
postId={listId} postId={listId}
isLastPost={isLastPost} isLastPost={isLastPost}
@ -77,6 +80,12 @@ function ThreadViewerRow({
timestampProps={timestampProps} timestampProps={timestampProps}
location={Locations.RHS_ROOT} location={Locations.RHS_ROOT}
/> />
{replyCount > 0 && (
<div className='root-post__divider'>
<div>{`${replyCount} Replies`}</div>
</div>
)}
</>
); );
case PostListUtils.isCombinedUserActivityPost(listId): { case PostListUtils.isCombinedUserActivityPost(listId): {
return ( return (

View File

@ -26,6 +26,7 @@ function getBasePropsAndState(): [Props, DeepPartial<GlobalState>] {
const currentUser = TestHelper.getUserMock({roles: 'role'}); const currentUser = TestHelper.getUserMock({roles: 'role'});
const post = TestHelper.getPostMock({ const post = TestHelper.getPostMock({
channel_id: channel.id, channel_id: channel.id,
reply_count: 0,
}); });
const directTeammate: UserProfile = TestHelper.getUserMock(); const directTeammate: UserProfile = TestHelper.getUserMock();

View File

@ -3,14 +3,17 @@
import {DynamicSizeList} from 'dynamic-virtualized-list'; import {DynamicSizeList} from 'dynamic-virtualized-list';
import type {OnScrollArgs, OnItemsRenderedArgs} from 'dynamic-virtualized-list'; import type {OnScrollArgs, OnItemsRenderedArgs} from 'dynamic-virtualized-list';
import React, {PureComponent} from 'react'; import React, {PureComponent, useMemo} from 'react';
import type {RefObject} from 'react'; import type {RefObject} from 'react';
import {useSelector} from 'react-redux';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import type {Channel} from '@mattermost/types/channels'; import type {Channel} from '@mattermost/types/channels';
import type {Post} from '@mattermost/types/posts'; import type {Post} from '@mattermost/types/posts';
import type {UserProfile} from '@mattermost/types/users'; import type {UserProfile} from '@mattermost/types/users';
import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {makeGetThreadOrSynthetic} from 'mattermost-redux/selectors/entities/threads';
import {isDateLine, isStartOfNewMessages, isCreateComment} from 'mattermost-redux/utils/post_list'; import {isDateLine, isStartOfNewMessages, isCreateComment} from 'mattermost-redux/utils/post_list';
import NewRepliesBanner from 'components/new_replies_banner'; import NewRepliesBanner from 'components/new_replies_banner';
@ -22,6 +25,7 @@ import DelayedAction from 'utils/delayed_action';
import {getNewMessageIndex, getPreviousPostId, getLatestPostId} from 'utils/post_utils'; import {getNewMessageIndex, getPreviousPostId, getLatestPostId} from 'utils/post_utils';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import type {GlobalState} from 'types/store';
import type {PluginComponent} from 'types/store/plugins'; import type {PluginComponent} from 'types/store/plugins';
import type {FakePost} from 'types/store/rhs'; import type {FakePost} from 'types/store/rhs';
@ -350,6 +354,14 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
const isLastPost = itemId === this.props.lastPost.id; const isLastPost = itemId === this.props.lastPost.id;
const isRootPost = itemId === this.props.selected.id; const isRootPost = itemId === this.props.selected.id;
const post = useSelector((state: GlobalState) => getPost(state, this.props.selected.id));
const getThreadOrSynthetic = useMemo(makeGetThreadOrSynthetic, []);
const totalReplies = useSelector((state: GlobalState) => {
const thread = getThreadOrSynthetic(state, post);
return thread.reply_count || 0;
});
if (!isDateLine(itemId) && !isStartOfNewMessages(itemId) && !isCreateComment(itemId) && !isRootPost) { if (!isDateLine(itemId) && !isStartOfNewMessages(itemId) && !isCreateComment(itemId) && !isRootPost) {
a11yIndex++; a11yIndex++;
} }
@ -379,6 +391,7 @@ class ThreadViewerVirtualized extends PureComponent<Props, State> {
isRootPost={isRootPost} isRootPost={isRootPost}
isLastPost={isLastPost} isLastPost={isLastPost}
listId={itemId} listId={itemId}
replyCount={totalReplies}
onCardClick={this.props.onCardClick} onCardClick={this.props.onCardClick}
previousPostId={getPreviousPostId(data, index)} previousPostId={getPreviousPostId(data, index)}
timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined} timestampProps={this.props.useRelativeTimestamp ? THREADING_TIME : undefined}

View File

@ -38,9 +38,37 @@
display: none; display: none;
} }
.root-post__divider {
position: relative;
display: flex;
height: 28px;
align-items: center;
margin: 0 0 4px 30px;
div {
z-index: 1;
padding: 0 12px;
margin-left: 34px;
background: rgba(v(center-channel-bg-rgb), 1);
color: rgba(v(center-channel-color-rgb), 0.72);
font-size: 12px;
font-weight: 600;
}
&::before {
position: absolute;
top: calc(50% - 1px);
left: 0;
display: block;
width: 100%;
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.12);
content: "";
}
}
.post { .post {
&.post--root { &.post--root {
padding-top: 2rem; padding-top: 16px;
.post__body { .post__body {
background: transparent !important; background: transparent !important;
@ -52,9 +80,9 @@
} }
.post-pre-header__icons-container { .post-pre-header__icons-container {
width: 60px; // If the width of post__img changes, this needs to be adjusted accordingly width: 54px; // If the width of post__img changes, this needs to be adjusted accordingly;
padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly;
margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly;
} }
.post__header { .post__header {
@ -119,8 +147,8 @@
} }
.post__img { .post__img {
width: 60px; // if this changes, the width of post-pre-header__icons-container needs to be adjusted accordingly width: 54px; // if this changes, the width of post-pre-header__icons-container needs to be adjusted accordingly;
padding: 2px 12px 0 0; // if the right padding changes, the padding of post-pre-header__icons-container needs to be adjusted accordingly padding: 2px 12px 0 0; // if the right padding changes, the padding of post-pre-header__icons-container needs to be adjusted accordingly;
} }
.post-body { .post-body {

View File

@ -440,7 +440,7 @@
.post { .post {
&.post--thread { &.post--thread {
.post-pre-header__icons-container { .post-pre-header__icons-container {
width: 60px; // If the width of post__img changes, this needs to be adjusted accordingly width: 44px; // If the width of post__img changes, this needs to be adjusted accordingly
padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly padding-right: 12px; // If the padding of post__img changes, this needs to be adjusted accordingly
margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly margin-left: 0; // if left margin of post__content changes, this needs to be adjusted accordingly
} }

View File

@ -94,6 +94,7 @@ export const getSelectedPost = createSelector(
message: localizeMessage('rhs_thread.rootPostDeletedMessage.body', 'Part of this thread has been deleted due to a data retention policy. You can no longer reply to this thread.'), message: localizeMessage('rhs_thread.rootPostDeletedMessage.body', 'Part of this thread has been deleted due to a data retention policy. You can no longer reply to this thread.'),
channel_id: selectedPostChannelId, channel_id: selectedPostChannelId,
user_id: currentUserId, user_id: currentUserId,
reply_count: 0,
}; };
}, },
); );

View File

@ -16,6 +16,7 @@ export type FakePost = {
exists: boolean; exists: boolean;
type: PostType; type: PostType;
message: string; message: string;
reply_count: number;
channel_id: Channel['id']; channel_id: Channel['id'];
user_id: UserProfile['id']; user_id: UserProfile['id'];
}; };