MM-56906: Remove redundant calls on team switch (#30771)

On page load, we load ALL channels and channel members from all teams.
But then, on team_switch, we would again load channels and channel
members from that team. This was redundant and mainly kept
because previously the websocket events were considered unreliable.

Now with reliable websockets, and client-side pings, we can detect
broken connections faster and recover without loss.

Additionally, the getAllChannelMembers call would page through
all responses on the client side. This was inefficient and incur
extra latency. To optimize for this, we introduce server-side
streaming of the full response if page is set to -1.

This optimizes the intial response as well.

https://mattermost.atlassian.net/browse/MM-56906

```release-note
Optimize team switch operation by removing calls to get channels
and channel members.
```


Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Agniva De Sarker
2025-05-12 20:05:46 +05:30
коммит произвёл GitHub
родитель 883711c72d
Коммит 4803892492
20 изменённых файлов: 376 добавлений и 49 удалений

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

@@ -34,6 +34,7 @@ import {
getChannelMemberCountsByGroup,
fetchAllMyChannelMembers,
fetchAllMyTeamsChannels,
fetchChannelsAndMembers,
} from 'mattermost-redux/actions/channels';
import {getCloudSubscription} from 'mattermost-redux/actions/cloud';
import {clearErrors, logError} from 'mattermost-redux/actions/errors';
@@ -882,6 +883,7 @@ async function handleTeamAddedEvent(msg) {
await dispatch(TeamActions.getMyTeamMembers());
const state = getState();
await dispatch(TeamActions.getMyTeamUnreads(isCollapsedThreadsEnabled(state)));
await dispatch(fetchChannelsAndMembers(msg.data.team_id));
const license = getLicense(state);
if (license.Cloud === 'true') {
dispatch(getTeamsUsage());

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

@@ -30,14 +30,6 @@ export function initializeTeam(team: Team): ActionFuncAsync<Team> {
const currentUser = getCurrentUser(state);
LocalStorageStore.setPreviousTeamId(currentUser.id, team.id);
try {
await dispatch(fetchChannelsAndMembers(team.id));
} catch (error) {
forceLogoutIfNecessary(error as ServerError, dispatch, getState);
dispatch(logError(error as ServerError));
return {error: error as ServerError};
}
const enabledUserStatuses = getIsUserStatusesConfigEnabled(state);
if (enabledUserStatuses) {
dispatch(addVisibleUsersInCurrentChannelAndSelfToStatusPoll());
@@ -99,6 +91,14 @@ export function joinTeam(teamname: string, joinedOnFirstLoad: boolean): ActionFu
await dispatch(initializeTeam(team));
try {
await dispatch(fetchChannelsAndMembers(team.id));
} catch (error) {
forceLogoutIfNecessary(error as ServerError, dispatch, getState);
dispatch(logError(error as ServerError));
return {error: error as ServerError};
}
return {data: team};
}
throw addUserToTeamResult.error;

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

@@ -59,7 +59,6 @@ function TeamController(props: Props) {
DesktopApp.reactAppInitialized();
async function fetchAllChannels() {
await props.fetchAllMyTeamsChannels();
setInitialChannelsLoaded(true);
}

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

@@ -2099,14 +2099,13 @@ describe('Actions.Channels', () => {
});
nock(Client4.getBaseRoute()).get(
'/users/some-user-id/channel_members?page=0&per_page=200').
reply(200, [...Array(200).keys()].map((index) => ({channel_id: `channel-${index}`, user_id: 'some-user-id'})));
nock(Client4.getBaseRoute()).get(
'/users/some-user-id/channel_members?page=1&per_page=200').
reply(200, [...Array(200).keys()].map((index) => ({channel_id: `channel-${index + 200}`, user_id: 'some-user-id'})));
nock(Client4.getBaseRoute()).get(
'/users/some-user-id/channel_members?page=2&per_page=200').
reply(200, [...Array(100).keys()].map((index) => ({channel_id: `channel-${index + 400}`, user_id: 'some-user-id'})));
'/users/some-user-id/channel_members?page=-1&per_page=60').
reply(200, [...Array(500).keys()].map((index) => (
{
channel_id: `channel-${index}`,
user_id: 'some-user-id',
roles: 'channel_user',
})));
await store.dispatch(Actions.fetchAllMyChannelMembers());
expect(Object.keys(store.getState().entities.channels.myMembers).length).toBe(500);

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

@@ -427,8 +427,8 @@ export function getChannelTimezones(channelId: string): ActionFuncAsync<string[]
export function fetchChannelsAndMembers(teamId: string): ActionFuncAsync<{channels: ServerChannel[]; channelMembers: ChannelMembership[]}> {
return async (dispatch, getState) => {
let channels;
let channelMembers;
let channels: ServerChannel[] = [];
let channelMembers: ChannelMembership[] = [];
try {
[channels, channelMembers] = await Promise.all([
Client4.getMyChannels(teamId),
@@ -471,32 +471,33 @@ export function fetchAllMyChannelMembers(): ActionFuncAsync {
const state = getState();
const {currentUserId} = state.entities.users;
let channelsMembers: ChannelMembership[] = [];
let hasMoreMembers = true;
let page = 0;
let channelMembers: ChannelMembership[] = [];
try {
while (hasMoreMembers) {
// Expected to disable since we don't have number of pages, so we can't use Promise.all
// eslint-disable-next-line no-await-in-loop
const data = await Client4.getAllChannelsMembers(currentUserId, page, 200);
channelsMembers = [...channelsMembers, ...data];
if (data.length < 200) {
hasMoreMembers = false;
}
page++;
}
// The server exposes a streaming API if page is set to -1
// We don't need to paginate through the responses, and thefore pageSize doesn't matter
channelMembers = await Client4.getAllChannelsMembers(currentUserId, -1);
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
}
const roles = new Set<string>();
for (const member of channelMembers) {
for (const role of member.roles.split(' ')) {
roles.add(role);
}
}
if (roles.size > 0) {
dispatch(loadRolesIfNeeded(roles));
}
dispatch({
type: ChannelTypes.RECEIVED_MY_CHANNEL_MEMBERS,
data: channelsMembers,
data: channelMembers,
currentUserId,
});
return {data: channelsMembers};
return {data: channelMembers};
};
}
@@ -512,7 +513,7 @@ export function fetchAllMyTeamsChannels(): ActionFuncAsync {
}
dispatch({
type: ChannelTypes.RECEIVED_ALL_CHANNELS,
type: ChannelTypes.RECEIVED_CHANNELS,
data: channels,
});
return {data: channels};