diff --git a/webapp/channels/src/actions/global_actions.tsx b/webapp/channels/src/actions/global_actions.tsx
index aa18c5a89e..cb22542fa4 100644
--- a/webapp/channels/src/actions/global_actions.tsx
+++ b/webapp/channels/src/actions/global_actions.tsx
@@ -65,7 +65,7 @@ export function emitChannelClickEvent(channel: Channel) {
const currentChannelId = getCurrentChannelId(state);
const previousRhsState = getPreviousRhsState(state);
- dispatch(getChannelStats(chan.id));
+ dispatch(getChannelStats(chan.id, true));
const penultimate = LocalStorageStore.getPreviousChannelName(userId, teamId);
const penultimateType = LocalStorageStore.getPreviousViewedType(userId, teamId);
diff --git a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx
index a9de0e72fd..4babb664d7 100644
--- a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx
@@ -3,8 +3,11 @@
import React from 'react';
-import {Channel, ChannelStats} from '@mattermost/types/channels';
+import {act} from '@testing-library/react';
+
import {renderWithIntl} from 'tests/react_testing_utils';
+
+import {Channel, ChannelStats} from '@mattermost/types/channels';
import {UserProfile} from '@mattermost/types/users';
import {Team} from '@mattermost/types/teams';
@@ -40,6 +43,7 @@ describe('channel_info_rhs', () => {
showChannelFiles: jest.fn(),
showPinnedPosts: jest.fn(),
showChannelMembers: jest.fn(),
+ getChannelStats: jest.fn().mockImplementation(() => Promise.resolve({data: {}})),
},
};
let props = {...OriginalProps};
@@ -49,20 +53,24 @@ describe('channel_info_rhs', () => {
});
describe('about area', () => {
- test('should be editable', () => {
+ test('should be editable', async () => {
renderWithIntl(
,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
expect(mockAboutArea).toHaveBeenCalledWith(
expect.objectContaining({
canEditChannelProperties: true,
}),
);
});
- test('should not be editable in archived channel', () => {
+ test('should not be editable in archived channel', async () => {
props.isArchived = true;
renderWithIntl(
@@ -71,6 +79,10 @@ describe('channel_info_rhs', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
expect(mockAboutArea).toHaveBeenCalledWith(
expect.objectContaining({
canEditChannelProperties: false,
diff --git a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx
index f16b9c77ea..7f75970911 100644
--- a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx
@@ -64,6 +64,7 @@ export interface Props {
showChannelFiles: (channelId: string) => void;
showPinnedPosts: (channelId: string | undefined) => void;
showChannelMembers: (channelId: string) => void;
+ getChannelStats: (channelId: string) => Promise<{data: ChannelStats}>;
};
}
@@ -192,6 +193,7 @@ const ChannelInfoRhs = ({
showChannelFiles: actions.showChannelFiles,
showPinnedPosts: actions.showPinnedPosts,
showChannelMembers: actions.showChannelMembers,
+ getChannelStats: actions.getChannelStats,
}}
/>
diff --git a/webapp/channels/src/components/channel_info_rhs/index.ts b/webapp/channels/src/components/channel_info_rhs/index.ts
index 09d6e8e49d..f30d8ebb8b 100644
--- a/webapp/channels/src/components/channel_info_rhs/index.ts
+++ b/webapp/channels/src/components/channel_info_rhs/index.ts
@@ -16,7 +16,7 @@ import {Constants, ModalIdentifiers} from 'utils/constants';
import {getCurrentUser} from 'mattermost-redux/selectors/entities/common';
import {getIsMobileView} from 'selectors/views/browser';
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
-import {unfavoriteChannel, favoriteChannel} from 'mattermost-redux/actions/channels';
+import {unfavoriteChannel, favoriteChannel, getChannelStats} from 'mattermost-redux/actions/channels';
import {muteChannel, unmuteChannel} from 'actions/channel_actions';
import {openModal} from 'actions/views/modals';
import {getDisplayNameByUser, getUserIdFromChannelId} from 'utils/utils';
@@ -92,6 +92,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
showChannelFiles,
showPinnedPosts,
showChannelMembers,
+ getChannelStats,
}, dispatch),
};
}
diff --git a/webapp/channels/src/components/channel_info_rhs/menu.test.tsx b/webapp/channels/src/components/channel_info_rhs/menu.test.tsx
index 1e4a8d5537..d68f9a2392 100644
--- a/webapp/channels/src/components/channel_info_rhs/menu.test.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/menu.test.tsx
@@ -2,12 +2,13 @@
// See LICENSE.txt for license information.
import React from 'react';
-import {fireEvent, screen} from '@testing-library/react';
+import {act, fireEvent, screen} from '@testing-library/react';
-import {Channel, ChannelStats} from '@mattermost/types/channels';
import {renderWithIntl} from 'tests/react_testing_utils';
import Constants from 'utils/constants';
+import {Channel, ChannelStats} from '@mattermost/types/channels';
+
import Menu from './menu';
describe('channel_info_rhs/menu', () => {
@@ -20,6 +21,7 @@ describe('channel_info_rhs/menu', () => {
showChannelFiles: jest.fn(),
showPinnedPosts: jest.fn(),
showChannelMembers: jest.fn(),
+ getChannelStats: jest.fn().mockImplementation(() => Promise.resolve({data: {files_count: 3, pinnedpost_count: 12, member_count: 32}})),
},
};
@@ -29,10 +31,11 @@ describe('channel_info_rhs/menu', () => {
showChannelFiles: jest.fn(),
showPinnedPosts: jest.fn(),
showChannelMembers: jest.fn(),
+ getChannelStats: jest.fn().mockImplementation(() => Promise.resolve({data: {files_count: 3, pinnedpost_count: 12, member_count: 32}})),
};
});
- test('should display notifications preferences', () => {
+ test('should display notifications preferences', async () => {
const props = {...defaultProps};
props.actions.openNotificationSettings = jest.fn();
@@ -42,13 +45,17 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
expect(screen.getByText('Notification Preferences')).toBeInTheDocument();
fireEvent.click(screen.getByText('Notification Preferences'));
expect(props.actions.openNotificationSettings).toHaveBeenCalled();
});
- test('should NOT display notifications preferences in a DM', () => {
+ test('should NOT display notifications preferences in a DM', async () => {
const props = {
...defaultProps,
channel: {type: Constants.DM_CHANNEL} as Channel,
@@ -60,10 +67,14 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
expect(screen.queryByText('Notification Preferences')).not.toBeInTheDocument();
});
- test('should NOT display notifications preferences in an archived channel', () => {
+ test('should NOT display notifications preferences in an archived channel', async () => {
const props = {
...defaultProps,
isArchived: true,
@@ -75,10 +86,14 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
expect(screen.queryByText('Notification Preferences')).not.toBeInTheDocument();
});
- test('should display the number of files', () => {
+ test('should display the number of files', async () => {
const props = {...defaultProps};
props.actions.showChannelFiles = jest.fn();
@@ -88,6 +103,10 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
const fileItem = screen.getByText('Files');
expect(fileItem).toBeInTheDocument();
expect(fileItem.parentElement).toHaveTextContent('3');
@@ -96,7 +115,7 @@ describe('channel_info_rhs/menu', () => {
expect(props.actions.showChannelFiles).toHaveBeenCalled();
});
- test('should display the pinned messages', () => {
+ test('should display the pinned messages', async () => {
const props = {...defaultProps};
props.actions.showPinnedPosts = jest.fn();
@@ -106,6 +125,10 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
const fileItem = screen.getByText('Pinned Messages');
expect(fileItem).toBeInTheDocument();
expect(fileItem.parentElement).toHaveTextContent('12');
@@ -114,7 +137,7 @@ describe('channel_info_rhs/menu', () => {
expect(props.actions.showPinnedPosts).toHaveBeenCalled();
});
- test('should display members', () => {
+ test('should display members', async () => {
const props = {...defaultProps};
props.actions.showChannelMembers = jest.fn();
@@ -124,6 +147,10 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
const membersItem = screen.getByText('Members');
expect(membersItem).toBeInTheDocument();
expect(membersItem.parentElement).toHaveTextContent('32');
@@ -132,7 +159,7 @@ describe('channel_info_rhs/menu', () => {
expect(props.actions.showChannelMembers).toHaveBeenCalled();
});
- test('should NOT display members in DM', () => {
+ test('should NOT display members in DM', async () => {
const props = {
...defaultProps,
channel: {type: Constants.DM_CHANNEL} as Channel,
@@ -144,6 +171,10 @@ describe('channel_info_rhs/menu', () => {
/>,
);
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
const membersItem = screen.queryByText('Members');
expect(membersItem).not.toBeInTheDocument();
});
diff --git a/webapp/channels/src/components/channel_info_rhs/menu.tsx b/webapp/channels/src/components/channel_info_rhs/menu.tsx
index 1092294e72..a45657c319 100644
--- a/webapp/channels/src/components/channel_info_rhs/menu.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/menu.tsx
@@ -1,12 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import React from 'react';
+import React, {useEffect, useState} from 'react';
import styled from 'styled-components';
import {useIntl} from 'react-intl';
import {Constants} from 'utils/constants';
+import LoadingSpinner from 'components/widgets/loading/loading_spinner';
+
import {Channel, ChannelStats} from '@mattermost/types/channels';
const MenuItemContainer = styled.div`
@@ -32,6 +34,9 @@ const RightSide = styled.div`
const Badge = styled.div`
font-size: 12px;
line-height: 18px;
+ width: 20px;
+ display: flex;
+ place-content: center;
`;
interface MenuItemProps {
@@ -39,7 +44,7 @@ interface MenuItemProps {
icon: JSX.Element;
text: string;
opensSubpanel?: boolean;
- badge?: string|number;
+ badge?: string|number|JSX.Element;
onClick: () => void;
}
@@ -94,14 +99,26 @@ interface MenuProps {
showChannelFiles: (channelId: string) => void;
showPinnedPosts: (channelId: string | undefined) => void;
showChannelMembers: (channelId: string) => void;
+ getChannelStats: (channelId: string) => Promise<{data: ChannelStats}>;
};
}
const Menu = ({channel, channelStats, isArchived, className, actions}: MenuProps) => {
const {formatMessage} = useIntl();
+ const [loadingStats, setLoadingStats] = useState(true);
const showNotificationPreferences = channel.type !== Constants.DM_CHANNEL && !isArchived;
const showMembers = channel.type !== Constants.DM_CHANNEL;
+ const fileCount = channelStats?.files_count >= 0 ? channelStats?.files_count : 0;
+
+ useEffect(() => {
+ actions.getChannelStats(channel.id).then(() => {
+ setLoadingStats(false);
+ });
+ return () => {
+ setLoadingStats(true);
+ };
+ }, [channel.id]);
return (
}
text={formatMessage({id: 'channel_info_rhs.menu.files', defaultMessage: 'Files'})}
opensSubpanel={true}
- badge={channelStats?.files_count}
+ badge={loadingStats ? : fileCount}
onClick={() => actions.showChannelFiles(channel.id)}
/>
diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts
index 29e07bd064..f5afb85aa2 100644
--- a/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts
+++ b/webapp/channels/src/packages/mattermost-redux/src/actions/channels.ts
@@ -8,7 +8,6 @@ import {ChannelTypes, PreferenceTypes, UserTypes} from 'mattermost-redux/action_
import {Client4} from 'mattermost-redux/client';
-import {General, Preferences} from '../constants';
import {CategoryTypes} from 'mattermost-redux/constants/channel_categories';
import {MarkUnread} from 'mattermost-redux/constants/channels';
@@ -25,12 +24,15 @@ import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {ActionFunc, ActionResult, DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';
+import {getChannelsIdForTeam, getChannelByName} from 'mattermost-redux/utils/channel_utils';
+
+import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
+
import {Channel, ChannelNotifyProps, ChannelMembership, ChannelModerationPatch, ChannelsWithTotalCount, ChannelSearchOpts} from '@mattermost/types/channels';
import {PreferenceType} from '@mattermost/types/preferences';
-import {getChannelsIdForTeam, getChannelByName} from 'mattermost-redux/utils/channel_utils';
-import {isMinimumServerVersion} from 'mattermost-redux/utils/helpers';
+import {General, Preferences} from '../constants';
import {addChannelToInitialCategory, addChannelToCategory} from './channel_categories';
import {logError} from './errors';
@@ -1074,11 +1076,11 @@ export function searchGroupChannels(term: string): ActionFunc {
});
}
-export function getChannelStats(channelId: string): ActionFunc {
+export function getChannelStats(channelId: string, excludeFilesCount?: boolean): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let stat;
try {
- stat = await Client4.getChannelStats(channelId);
+ stat = await Client4.getChannelStats(channelId, excludeFilesCount);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
diff --git a/webapp/platform/client/src/client4.ts b/webapp/platform/client/src/client4.ts
index 56e2f552f1..21a1a8798f 100644
--- a/webapp/platform/client/src/client4.ts
+++ b/webapp/platform/client/src/client4.ts
@@ -1782,9 +1782,10 @@ export default class Client4 {
);
};
- getChannelStats = (channelId: string) => {
+ getChannelStats = (channelId: string, excludeFilesCount = false) => {
+ const param = excludeFilesCount ? `?exclude_files_count=${excludeFilesCount}` : '';
return this.doFetch(
- `${this.getChannelRoute(channelId)}/stats`,
+ `${this.getChannelRoute(channelId)}/stats${param}`,
{method: 'get'},
);
};