[GH-30077] New actions for websocket group member update (#30086)

* Added: Update on `groups` & `profiles` on websocket.
Этот коммит содержится в:
Andy Lerandy
2025-03-20 14:28:46 +01:00
коммит произвёл GitHub
родитель 12cbe5e839
Коммит 7770c03919
8 изменённых файлов: 220 добавлений и 27 удалений

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

@@ -1449,30 +1449,39 @@ function handleGroupUpdatedEvent(msg) {
);
}
function handleMyGroupUpdate(groupMember) {
dispatch(batchActions([
{
type: GroupTypes.ADD_MY_GROUP,
id: groupMember.group_id,
},
{
type: GroupTypes.RECEIVED_MEMBER_TO_ADD_TO_GROUP,
data: groupMember,
id: groupMember.group_id,
},
{
type: UserTypes.RECEIVED_PROFILES_FOR_GROUP,
data: [groupMember],
id: groupMember.group_id,
},
]));
}
export function handleGroupAddedMemberEvent(msg) {
return async (doDispatch, doGetState) => {
const state = doGetState();
const currentUserId = getCurrentUserId(state);
const groupInfo = JSON.parse(msg.data.group_member);
const groupMember = JSON.parse(msg.data.group_member);
if (currentUserId === groupInfo.user_id) {
const group = getGroup(state, groupInfo.group_id);
if (currentUserId === groupMember.user_id) {
const group = getGroup(state, groupMember.group_id);
if (group) {
dispatch(
{
type: GroupTypes.ADD_MY_GROUP,
id: groupInfo.group_id,
},
);
handleMyGroupUpdate(groupMember);
} else {
const {error} = await doDispatch(fetchGroup(groupInfo.group_id, true));
const {error} = await doDispatch(fetchGroup(groupMember.group_id, true));
if (!error) {
dispatch(
{
type: GroupTypes.ADD_MY_GROUP,
id: groupInfo.group_id,
},
);
handleMyGroupUpdate(groupMember);
}
}
}
@@ -1486,13 +1495,23 @@ function handleGroupDeletedMemberEvent(msg) {
const data = JSON.parse(msg.data.group_member);
if (currentUserId === data.user_id) {
dispatch(
dispatch(batchActions([
{
type: GroupTypes.REMOVE_MY_GROUP,
data,
id: data.group_id,
},
);
{
type: UserTypes.RECEIVED_PROFILES_LIST_TO_REMOVE_FROM_GROUP,
data: [data],
id: data.group_id,
},
{
type: GroupTypes.RECEIVED_MEMBER_TO_REMOVE_FROM_GROUP,
data,
id: data.group_id,
},
]));
}
};
}

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

@@ -270,8 +270,34 @@ describe('handleGroupAddedMemberEvent', () => {
testStore.dispatch(handleGroupAddedMemberEvent(msg));
expect(store.dispatch).toHaveBeenCalledWith({
type: 'ADD_MY_GROUP',
id: 'group-1',
meta: {batch: true},
payload: [
{
type: 'ADD_MY_GROUP',
id: 'group-1',
},
{
data: {
create_at: 1691178673417,
delete_at: 0,
group_id: 'group-1',
user_id: 'currentUserId',
},
id: 'group-1',
type: 'RECEIVED_MEMBER_TO_ADD_TO_GROUP',
},
{
data: [{
create_at: 1691178673417,
delete_at: 0,
group_id: 'group-1',
user_id: 'currentUserId',
}],
id: 'group-1',
type: 'RECEIVED_PROFILES_FOR_GROUP',
},
],
type: 'BATCHING_REDUCER.BATCH',
});
});

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

@@ -37,6 +37,9 @@ export default keyMirror({
RECEIVED_GROUP_NOT_ASSOCIATED_TO_CHANNEL: null,
RECEIVED_GROUPS_NOT_ASSOCIATED_TO_CHANNEL: null,
RECEIVED_MEMBER_TO_REMOVE_FROM_GROUP: null,
RECEIVED_MEMBER_TO_ADD_TO_GROUP: null,
PATCHED_GROUP_TEAM: null,
PATCHED_GROUP_CHANNEL: null,

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

@@ -4,9 +4,8 @@
import type {AnyAction} from 'redux';
import {batchActions} from 'redux-batched-actions';
import type {GroupPatch, SyncablePatch, GroupCreateWithUserIds, CustomGroupPatch, GroupSearchParams, GetGroupsParams, GetGroupsForUserParams, Group} from '@mattermost/types/groups';
import type {GroupPatch, SyncablePatch, GroupCreateWithUserIds, CustomGroupPatch, GroupSearchParams, GetGroupsParams, GetGroupsForUserParams, Group, GroupMember} from '@mattermost/types/groups';
import {SyncableType, GroupSource} from '@mattermost/types/groups';
import type {UserProfile} from '@mattermost/types/users';
import {ChannelTypes, GroupTypes, UserTypes} from 'mattermost-redux/action_types';
import {Client4} from 'mattermost-redux/client';
@@ -344,7 +343,7 @@ export function createGroupWithUserIds(group: GroupCreateWithUserIds): ActionFun
};
}
export function addUsersToGroup(groupId: string, userIds: string[]): ActionFuncAsync<UserProfile[]> {
export function addUsersToGroup(groupId: string, userIds: string[]): ActionFuncAsync<GroupMember[]> {
return async (dispatch, getState) => {
let data;
try {
@@ -366,7 +365,7 @@ export function addUsersToGroup(groupId: string, userIds: string[]): ActionFuncA
};
}
export function removeUsersFromGroup(groupId: string, userIds: string[]): ActionFuncAsync<UserProfile[]> {
export function removeUsersFromGroup(groupId: string, userIds: string[]): ActionFuncAsync<GroupMember[]> {
return async (dispatch, getState) => {
let data;
try {

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

@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Group} from '@mattermost/types/groups';
import {GroupTypes} from 'mattermost-redux/action_types';
import reducer from 'mattermost-redux/reducers/entities/groups';
@@ -341,4 +343,100 @@ describe('reducers/entities/groups', () => {
expect(newState.syncables).toEqual(expectedState.syncables);
});
});
describe('Groups', () => {
const groupTemplate: Omit<Group, 'id' | 'member_count' | 'member_ids'> = {
name: 'Test Group',
display_name: 'Test Group',
description: 'Test Description',
source: '',
create_at: 1,
update_at: 1,
delete_at: 1,
has_syncables: false,
scheme_admin: false,
allow_reference: false,
remote_id: null,
};
const groupId = 'ge63nq31sbfy3duzq5f7yqn7ii';
const userId = 'o3tdawqxot8kikzq8bk54zggbc';
const stateTemplate = {
syncables: {},
stats: {},
myGroups: [],
};
const receivedMemberToAddAction = {
type: GroupTypes.RECEIVED_MEMBER_TO_ADD_TO_GROUP,
data: {
user_id: userId,
group_id: groupId,
},
id: groupId,
};
const receivedMemberToDelAction = {...receivedMemberToAddAction, type: GroupTypes.RECEIVED_MEMBER_TO_REMOVE_FROM_GROUP};
it('GroupTypes.RECEIVED_MEMBER_TO_ADD_TO_GROUP, with member list', () => {
const state = {
...stateTemplate,
groups: {
[userId]: {...groupTemplate, id: userId, member_ids: [], member_count: 0},
[groupId]: {...groupTemplate, id: groupId, member_ids: [], member_count: 0},
},
};
const expectedState = {
...stateTemplate,
groups: {
[userId]: {...groupTemplate, id: userId, member_ids: [], member_count: 0},
[groupId]: {...groupTemplate, id: groupId, member_ids: [userId], member_count: 1},
},
};
const newState = reducer(state, receivedMemberToAddAction);
expect(newState.groups).toEqual(expectedState.groups);
});
it('GroupTypes.RECEIVED_MEMBER_TO_ADD_TO_GROUP, doublon', () => {
const state = {
...stateTemplate,
groups: {
[groupId]: {...groupTemplate, id: groupId, member_ids: [userId], member_count: 1},
},
};
const newState = reducer(state, receivedMemberToAddAction);
expect(newState.groups).toEqual(state.groups);
});
it('GroupTypes.RECEIVED_MEMBER_TO_REMOVE_FROM_GROUP, with member list', () => {
const state = {
...stateTemplate,
groups: {
[userId]: {...groupTemplate, id: userId, member_ids: [], member_count: 0},
[groupId]: {...groupTemplate, id: groupId, member_ids: [userId], member_count: 1},
},
};
const expectedState = {
...stateTemplate,
groups: {
[userId]: {...groupTemplate, id: userId, member_ids: [], member_count: 0},
[groupId]: {...groupTemplate, id: groupId, member_ids: [], member_count: 0},
},
};
const newState = reducer(state, receivedMemberToDelAction);
expect(newState.groups).toEqual(expectedState.groups);
});
it('GroupTypes.RECEIVED_MEMBER_TO_REMOVE_FROM_GROUP, Not existant', () => {
const state = {
...stateTemplate,
groups: {
[groupId]: {...groupTemplate, id: groupId, member_ids: [groupId], member_count: 1},
},
};
const newState = reducer(state, receivedMemberToDelAction);
expect(newState.groups).toEqual(state.groups);
});
});
});

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

@@ -3,7 +3,7 @@
import {combineReducers} from 'redux';
import type {GroupChannel, GroupSyncablesState, GroupTeam, Group} from '@mattermost/types/groups';
import type {GroupChannel, GroupSyncablesState, GroupTeam, Group, GroupMember} from '@mattermost/types/groups';
import type {MMReduxAction} from 'mattermost-redux/action_types';
import {GroupTypes} from 'mattermost-redux/action_types';
@@ -243,6 +243,46 @@ function groups(state: Record<string, Group> = {}, action: MMReduxAction) {
return nextState;
}
case GroupTypes.RECEIVED_MEMBER_TO_REMOVE_FROM_GROUP: {
const dataInfo: GroupMember = action.data;
const group = state[dataInfo.group_id];
if (Array.isArray(group?.member_ids)) {
const newMemberIds = new Set(group.member_ids);
newMemberIds.delete(dataInfo.user_id);
const newGroup = {...group,
member_ids: [...newMemberIds],
member_count: newMemberIds.size,
};
return {
...state,
[group.id]: newGroup,
};
}
return state;
}
case GroupTypes.RECEIVED_MEMBER_TO_ADD_TO_GROUP: {
const {group_id: groupId, user_id: userId}: GroupMember = action.data;
const group = state[groupId];
if (Array.isArray(group?.member_ids)) {
const newMemberIds = new Set(group.member_ids);
newMemberIds.add(userId);
const newGroup = {...group,
member_ids: [...newMemberIds],
member_count: newMemberIds.size,
};
return {
...state,
[group.id]: newGroup,
};
}
return state;
}
default:
return state;
}

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

@@ -77,6 +77,7 @@ import type {
GetGroupsParams,
GetGroupsForUserParams,
GroupStats,
GroupMember,
} from '@mattermost/types/groups';
import type {PostActionResponse} from '@mattermost/types/integration_actions';
import type {
@@ -3745,14 +3746,14 @@ export default class Client4 {
};
addUsersToGroup = (groupId: string, userIds: string[]) => {
return this.doFetch<UserProfile[]>(
return this.doFetch<GroupMember[]>(
`${this.getGroupRoute(groupId)}/members`,
{method: 'post', body: JSON.stringify({user_ids: userIds})},
);
};
removeUsersFromGroup = (groupId: string, userIds: string[]) => {
return this.doFetch<UserProfile[]>(
return this.doFetch<GroupMember[]>(
`${this.getGroupRoute(groupId)}/members`,
{method: 'delete', body: JSON.stringify({user_ids: userIds})},
);

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

@@ -182,6 +182,13 @@ export type GroupSearchParams = GetGroupsParams & {
include_channel_member_count?: string;
}
export type GroupMember = {
group_id: string;
user_id: string;
create_at: number;
deleted_at: number;
}
export type GroupMembership = {
user_id: string;
roles: string;