* Fixed a bug where GM member count shows up as 0 in scheduled posts

* Fixed a bug where GM member count shows up as 0 in scheduled posts

* Lint fix

* Added wrap̛ntl to channels

* lint fix

* Used existing renderer

* Removed ubnused store
Этот коммит содержится в:
Harshil Sharma
2024-11-13 16:33:25 +05:30
коммит произвёл GitHub
родитель 053d0b5f0a
Коммит de6701fb99
6 изменённых файлов: 255 добавлений и 427 удалений

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

@@ -1,359 +1,130 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/drafts/draft_actions should fetch members when member count is 0 for GM 1`] = `
<div>
To:
<div
class="DraftTitle__group-icon"
>
0
</div>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType=""
membersCount={5}
selfDraft={false}
teammate={Object {}}
teammateId=""
type=""
<div>
In:
<i
class="icon icon-globe"
/>
</ContextProvider>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for DM channel 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType="D"
membersCount={5}
selfDraft={false}
teammate={Object {}}
teammateId=""
type=""
<div>
To:
<img
alt="user profile image"
class="Avatar Avatar-xs DraftTitle__avatar"
loading="lazy"
src="/api/v4/users/undefined/image?_=0"
/>
</ContextProvider>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for DM channel with teammate 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType="D"
membersCount={5}
selfDraft={false}
teammate={
Object {
"id": "id",
"last_picture_update": 1000,
"username": "username",
}
}
teammateId=""
type=""
<div>
To:
<img
alt="username profile image"
class="Avatar Avatar-xs DraftTitle__avatar"
loading="lazy"
src="/api/v4/users/id/image?_=1000"
/>
</ContextProvider>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for GM channel 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType="G"
membersCount={5}
selfDraft={false}
teammate={Object {}}
teammateId=""
type=""
/>
</ContextProvider>
<div>
To:
<div
class="DraftTitle__group-icon"
>
5
</div>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for open channel 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType="O"
membersCount={5}
selfDraft={false}
teammate={Object {}}
teammateId=""
type="channel"
<div>
In:
<i
class="icon icon-globe"
/>
</ContextProvider>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for private channel 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType="P"
membersCount={5}
selfDraft={false}
teammate={Object {}}
teammateId=""
type=""
<div>
In:
<i
class="icon icon-lock-outline"
/>
</ContextProvider>
<span>
Test Channel
</span>
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for self draft 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType=""
membersCount={5}
selfDraft={true}
teammate={Object {}}
teammateId=""
type=""
<div>
In:
<i
class="icon icon-globe"
/>
</ContextProvider>
<span>
Test Channel
</span>
 
(you)
</div>
`;
exports[`components/drafts/draft_actions should match snapshot for thread 1`] = `
<ContextProvider
value={
Object {
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Subscription {
"handleChangeWrapper": [Function],
"listeners": Object {
"notify": [Function],
},
"onStateChange": [Function],
"parentSub": undefined,
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"unsubscribe": null,
},
}
}
>
<Memo(DraftTitle)
channelName=""
channelType="O"
membersCount={5}
selfDraft={false}
teammate={Object {}}
teammateId=""
type="thread"
<div>
Thread in:
<i
class="icon icon-globe"
/>
</ContextProvider>
<span>
Test Channel
</span>
</div>
`;

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

@@ -1,102 +1,98 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Provider} from 'react-redux';
import type {Channel} from '@mattermost/types/channels';
import type {UserProfile} from '@mattermost/types/users';
import mockStore from 'tests/test_store';
import {renderWithContext} from 'tests/react_testing_utils';
import Constants from 'utils/constants';
import DraftTitle from './draft_title';
describe('components/drafts/draft_actions', () => {
const channel = {
type: 'O',
display_name: 'Test Channel',
} as Channel;
const baseProps = {
channelType: '' as Channel['type'],
channelName: '',
channel,
membersCount: 5,
selfDraft: false,
teammate: {} as UserProfile,
teammateId: '',
type: '' as 'channel' | 'thread',
type: 'channel' as 'channel' | 'thread',
};
it('should match snapshot', () => {
const store = mockStore();
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...baseProps}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for self draft', () => {
const store = mockStore();
const props = {
...baseProps,
selfDraft: true,
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for private channel', () => {
const store = mockStore();
const channel = {
type: Constants.PRIVATE_CHANNEL,
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channelType: Constants.PRIVATE_CHANNEL as Channel['type'],
channel,
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for DM channel', () => {
const store = mockStore();
const channel = {
type: Constants.DM_CHANNEL,
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channelType: Constants.DM_CHANNEL as Channel['type'],
channel,
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for DM channel with teammate', () => {
const store = mockStore();
const channel = {
type: Constants.DM_CHANNEL,
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channelType: Constants.DM_CHANNEL as Channel['type'],
channel,
teammate: {
username: 'username',
id: 'id',
@@ -104,69 +100,91 @@ describe('components/drafts/draft_actions', () => {
} as UserProfile,
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for GM channel', () => {
const store = mockStore();
const channel = {
type: 'G',
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channelType: Constants.GM_CHANNEL as Channel['type'],
channel,
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for thread', () => {
const store = mockStore();
const channel = {
type: Constants.OPEN_CHANNEL,
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channelType: Constants.OPEN_CHANNEL as Channel['type'],
channel,
type: 'thread' as 'channel' | 'thread',
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for open channel', () => {
const store = mockStore();
const channel = {
type: Constants.OPEN_CHANNEL,
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channelType: Constants.OPEN_CHANNEL as Channel['type'],
channel,
type: 'channel' as 'channel' | 'thread',
};
const wrapper = shallow(
<Provider store={store}>
<DraftTitle
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should fetch members when member count is 0 for GM', () => {
const channel = {
type: 'G',
display_name: 'Test Channel',
} as Channel;
const props = {
...baseProps,
channel,
membersCount: 0,
type: 'channel' as 'channel' | 'thread',
};
const {container} = renderWithContext(
<DraftTitle
{...props}
/>,
);
expect(container).toMatchSnapshot();
});
});

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

@@ -8,7 +8,7 @@ import {useDispatch} from 'react-redux';
import type {Channel} from '@mattermost/types/channels';
import type {UserProfile} from '@mattermost/types/users';
import {getMissingProfilesByIds} from 'mattermost-redux/actions/users';
import {batchGetProfilesInChannel, getMissingProfilesByIds} from 'mattermost-redux/actions/users';
import Avatar from 'components/widgets/users/avatar';
@@ -18,8 +18,7 @@ import {imageURLForUser} from 'utils/utils';
import './draft_title.scss';
type Props = {
channelType: Channel['type'];
channelName: string;
channel: Channel;
membersCount?: number;
selfDraft: boolean;
teammate?: UserProfile;
@@ -28,8 +27,7 @@ type Props = {
}
function DraftTitle({
channelType,
channelName,
channel,
membersCount,
selfDraft,
teammate,
@@ -44,6 +42,19 @@ function DraftTitle({
}
}, [teammate?.id, teammateId]);
useEffect(() => {
// if you have a scheduled post in a GM and you closed that GM,
// we don't fetch that GM's members by default. This causes the number of GM members
// in scheduled posts row header to show up as '0'. To fix this,
// we check if the channel is a GM and member count is 0 (could will at least be 1 as the current user
// is always a member) and if so, fetch the GM members.
// The action uses a data loader so it is safe to call do this for multiple
// scheduled posts for the same GM without causing any duplicate API calls.
if (channel.type === Constants.GM_CHANNEL && !membersCount) {
dispatch(batchGetProfilesInChannel(channel.id));
}
}, [channel.id, channel.type, dispatch, membersCount]);
let you = null;
let title = null;
@@ -61,11 +72,11 @@ function DraftTitle({
let icon = <i className='icon icon-globe'/>;
if (channelType === Constants.PRIVATE_CHANNEL) {
if (channel.type === Constants.PRIVATE_CHANNEL) {
icon = <i className='icon icon-lock-outline'/>;
}
if (channelType === Constants.DM_CHANNEL && teammate) {
if (channel.type === Constants.DM_CHANNEL && teammate) {
icon = (
<Avatar
size='xs'
@@ -76,7 +87,7 @@ function DraftTitle({
);
}
if (channelType === Constants.GM_CHANNEL) {
if (channel.type === Constants.GM_CHANNEL) {
icon = (
<div className='DraftTitle__group-icon'>
{membersCount}
@@ -86,8 +97,8 @@ function DraftTitle({
if (type === 'thread') {
if (
channelType !== Constants.GM_CHANNEL &&
channelType !== Constants.DM_CHANNEL
channel.type !== Constants.GM_CHANNEL &&
channel.type !== Constants.DM_CHANNEL
) {
title = (
<FormattedMessage
@@ -95,7 +106,7 @@ function DraftTitle({
defaultMessage={'Thread in: {icon} <span>{channelName}</span>'}
values={{
icon,
channelName,
channelName: channel.display_name,
span: (chunks: React.ReactNode) => (<span>{chunks}</span>),
}}
/>
@@ -107,15 +118,15 @@ function DraftTitle({
defaultMessage={'Thread to: {icon} <span>{channelName}</span>'}
values={{
icon,
channelName,
channelName: channel.display_name,
span: (chunks: React.ReactNode) => (<span>{chunks}</span>),
}}
/>
);
}
} else if (
channelType !== Constants.GM_CHANNEL &&
channelType !== Constants.DM_CHANNEL
channel.type !== Constants.GM_CHANNEL &&
channel.type !== Constants.DM_CHANNEL
) {
title = (
<FormattedMessage
@@ -123,7 +134,7 @@ function DraftTitle({
defaultMessage={'In: {icon} <span>{channelName}</span>'}
values={{
icon,
channelName,
channelName: channel.display_name,
span: (chunks: React.ReactNode) => (<span>{chunks}</span>),
}}
/>
@@ -135,7 +146,7 @@ function DraftTitle({
defaultMessage={'To: {icon} <span>{channelName}</span>'}
values={{
icon,
channelName,
channelName: channel.display_name,
span: (chunks: React.ReactNode) => (<span>{chunks}</span>),
}}
/>

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

@@ -25,7 +25,6 @@ function makeMapStateToProps() {
return (state: GlobalState, ownProps: OwnProps) => {
const {channel, userId} = ownProps;
const channelName = channel.display_name;
let teammateId;
let teammate;
let membersCount;
@@ -40,8 +39,7 @@ function makeMapStateToProps() {
}
return {
channelName,
channelType: channel.type,
channel,
membersCount,
selfDraft: teammateId === userId,
teammate,

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

@@ -5,6 +5,7 @@ import type {AnyAction} from 'redux';
import {batchActions} from 'redux-batched-actions';
import type {UserAutocomplete} from '@mattermost/types/autocomplete';
import type {Channel} from '@mattermost/types/channels';
import type {ServerError} from '@mattermost/types/errors';
import type {UserProfile, UserStatus, GetFilteredUsersStatsOpts, UsersStats, UserCustomStatus, UserAccessToken} from '@mattermost/types/users';
@@ -375,6 +376,21 @@ export function getProfilesInChannel(channelId: string, page: number, perPage: n
};
}
export function batchGetProfilesInChannel(channelId: string): ActionFuncAsync<Array<Channel['id']>> {
return async (dispatch, getState, {loaders}: any) => {
if (!loaders.profilesInChannelLoader) {
loaders.profilesInChannelLoader = new DelayedDataLoader<Channel['id']>({
fetchBatch: (channelIds) => dispatch(getProfilesInChannel(channelIds[0], 0)),
maxBatchSize: 1,
wait: missingProfilesWait,
});
}
await loaders.profilesInChannelLoader.queueAndWait([channelId]);
return {};
};
}
export function getProfilesInGroupChannels(channelsIds: string[]): ActionFuncAsync {
return async (dispatch, getState) => {
let channelProfiles;

14
webapp/channels/src/utils/test_intl.tsx Обычный файл
Просмотреть файл

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ReactNode} from 'react';
import React from 'react';
import {IntlProvider, createIntl} from 'react-intl';
export const defaultIntl = createIntl({
locale: 'en',
defaultLocale: 'en',
messages: {},
});
export const wrapIntl = (children?: ReactNode) => <IntlProvider {...defaultIntl}>{children}</IntlProvider>;