MM-54581 Fixed a bug where converted GM showed up in incorrect team as well when someone else converted it (#24633)

* Fixed a bug where converted GM showed up in incorrect team as well when someone else converted it

* Created new redux action

* Fixed test

* Unexported fjunction

* More tests

* Using old state instead new state as data exists in old, not new state
This commit is contained in:
Harshil Sharma 2023-10-05 11:10:43 +05:30 committed by GitHub
parent 8e7e5c8775
commit 45ccd50f8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 5 deletions

View File

@ -622,9 +622,22 @@ export function handleChannelUpdatedEvent(msg) {
return (doDispatch, doGetState) => {
const channel = JSON.parse(msg.data.channel);
doDispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel});
const actions = [{type: ChannelTypes.RECEIVED_CHANNEL, data: channel}];
// handling the case of GM converted to private channel.
const state = doGetState();
const existingChannel = getChannel(state, channel.id);
// if the updated channel exists in store
if (existingChannel) {
// and it was a GM, converted to a private channel
if (existingChannel.type === General.GM_CHANNEL && channel.type === General.PRIVATE_CHANNEL) {
actions.push({type: ChannelTypes.GM_CONVERTED_TO_CHANNEL, data: channel});
}
}
doDispatch(batchActions(actions));
if (channel.id === getCurrentChannelId(state)) {
// using channel's team_id to ensure we always redirect to current channel even if channel's team changes.
getHistory().replace(`${getRelativeTeamUrl(state, channel.team_id)}/channels/${channel.name}`);

View File

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ChannelTypes, UserTypes, CloudTypes} from 'mattermost-redux/action_types';
import {UserTypes, CloudTypes} from 'mattermost-redux/action_types';
import {getGroup} from 'mattermost-redux/actions/groups';
import {
getMentionsAndStatusesForPosts,
@ -636,6 +636,11 @@ describe('handleChannelUpdatedEvent', () => {
entities: {
channels: {
currentChannelId: 'channel',
channels: {
channel: {
id: 'channel',
},
},
},
teams: {
currentTeamId: 'team',
@ -656,10 +661,56 @@ describe('handleChannelUpdatedEvent', () => {
const msg = {data: {channel: JSON.stringify(channel)}};
testStore.dispatch(handleChannelUpdatedEvent(msg));
expect(testStore.getActions()).toEqual([{
type: 'BATCHING_REDUCER.BATCH',
meta: {batch: true},
payload: [
{
type: 'RECEIVED_CHANNEL',
data: {
id: 'channel',
team_id: 'team',
},
},
],
}]);
});
expect(testStore.getActions()).toEqual([
{type: ChannelTypes.RECEIVED_CHANNEL, data: channel},
]);
test('when GM is converted to private channel', () => {
const state = initialState;
state.entities.channels.channels.channel.type = Constants.GM_CHANNEL;
const testStore = configureStore(state);
const channel = {
id: 'channel',
team_id: 'team',
type: Constants.PRIVATE_CHANNEL,
};
const msg = {data: {channel: JSON.stringify(channel)}};
testStore.dispatch(handleChannelUpdatedEvent(msg));
expect(testStore.getActions()).toEqual([{
type: 'BATCHING_REDUCER.BATCH',
meta: {batch: true},
payload: [
{
type: 'RECEIVED_CHANNEL',
data: {
id: 'channel',
team_id: 'team',
type: 'P',
},
},
{
type: 'GM_CONVERTED_TO_CHANNEL',
data: {
id: 'channel',
team_id: 'team',
type: 'P',
},
},
],
}]);
});
test('should not change URL when current channel is updated', () => {

View File

@ -95,4 +95,6 @@ export default keyMirror({
DECREMENT_PINNED_POST_COUNT: null,
INCREMENT_FILE_COUNT: null,
GM_CONVERTED_TO_CHANNEL: null,
});

View File

@ -4,6 +4,7 @@
import {combineReducers} from 'redux';
import type {ChannelCategory} from '@mattermost/types/channel_categories';
import type {Channel} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {IDMappedObjects, RelationOneToOne} from '@mattermost/types/utilities';
@ -92,7 +93,38 @@ export function byId(state: IDMappedObjects<ChannelCategory> = {}, action: Gener
return changed ? nextState : state;
}
case ChannelTypes.GM_CONVERTED_TO_CHANNEL: {
// For GM to Private channel conversion feature
// In the case when someone converts your GM to a private channel and moves it to a team
// you're not currently on, we need to remove the channel from "direct messages" category
// and add it to "channels" category of target team. Even though the server sends a websocket event about updated category data,
// it does so only for the team the channel got moved into, and not every team a user is part of, for performance reasons.
// For every other team, we update the state here. Everything is correct on server side, but we update state here
// to avoid re-fetching all categories again for all teams.
const receivedChannel = action.data as Channel;
const newState: IDMappedObjects<ChannelCategory> = {};
const categoryIDs = Object.keys(state);
categoryIDs.forEach((categoryID) => {
if (categoryID.startsWith('channels_') && state[categoryID].team_id === receivedChannel.team_id && state[categoryID].channel_ids.indexOf(receivedChannel.id) < 0) {
// We don't need to worry about adding the channel in the right order as this is only
// an intermediate step, meant to handle the edge case of missing the upcoming "update category"
// websocket message, triggered on conversion of GM to private channel.
newState[categoryID] = {
...state[categoryID],
channel_ids: [...state[categoryID].channel_ids, receivedChannel.id],
};
} else {
newState[categoryID] = {
...state[categoryID],
channel_ids: state[categoryID].channel_ids.filter((channelID) => channelID !== receivedChannel.id),
};
}
});
return newState;
}
case UserTypes.LOGOUT_SUCCESS:
return {};
default: