Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rotten-peaches-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Removes the Leader role from the room header. Leaders are now displayed in their respective group within the room's members list.
9 changes: 9 additions & 0 deletions .changeset/weak-kangaroos-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@rocket.chat/model-typings': minor
'@rocket.chat/core-typings': minor
'@rocket.chat/models': minor
'@rocket.chat/i18n': minor
'@rocket.chat/meteor': minor
---

Adds the Leader group to rooms' members list for better role visibility and consistency.
3 changes: 2 additions & 1 deletion apps/meteor/app/lib/server/functions/createRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { Meteor } from 'meteor/meteor';
import { createDirectRoom } from './createDirectRoom';
import { callbacks } from '../../../../lib/callbacks';
import { beforeCreateRoomCallback } from '../../../../lib/callbacks/beforeCreateRoomCallback';
import { calculateRoomRolePriorityFromRoles } from '../../../../lib/roles/calculateRoomRolePriorityFromRoles';
import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig';
import { calculateRoomRolePriorityFromRoles, syncRoomRolePriorityForUserAndRoom } from '../../../../server/lib/roles/syncRoomRolePriority';
import { syncRoomRolePriorityForUserAndRoom } from '../../../../server/lib/roles/syncRoomRolePriority';
import { getDefaultSubscriptionPref } from '../../../utils/lib/getDefaultSubscriptionPref';
import { getValidRoomName } from '../../../utils/server/lib/getValidRoomName';
import { notifyOnRoomChanged, notifyOnSubscriptionChangedById } from '../lib/notifyListener';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { IRoom, IUser } from '@rocket.chat/core-typings';
import { Subscriptions, Users, Rooms } from '@rocket.chat/models';

import { calculateRoomRolePriorityFromRoles } from '../../../../server/lib/roles/syncRoomRolePriority';
import { calculateRoomRolePriorityFromRoles } from '../../../../lib/roles/calculateRoomRolePriorityFromRoles';

const READ_BATCH_SIZE = 1000;

const SYNC_VERSION = 2;

async function assignRoomRolePrioritiesFromMap(userIdAndRoomRolePrioritiesMap: Map<IUser['_id'], IUser['roomRolePriorities']>) {
const bulk = Users.col.initializeUnorderedBulkOp();

Expand Down Expand Up @@ -35,7 +37,7 @@ async function assignRoomRolePrioritiesFromMap(userIdAndRoomRolePrioritiesMap: M
export const syncRolePrioritiesForRoomIfRequired = async (rid: IRoom['_id']) => {
const userIdAndRoomRolePrioritiesMap = new Map<IUser['_id'], IUser['roomRolePriorities']>();

if (await Rooms.hasCreatedRolePrioritiesForRoom(rid)) {
if (await Rooms.hasCreatedRolePrioritiesForRoom(rid, SYNC_VERSION)) {
return;
}

Expand Down Expand Up @@ -64,5 +66,5 @@ export const syncRolePrioritiesForRoomIfRequired = async (rid: IRoom['_id']) =>
// Flush any remaining priorities in the map
await assignRoomRolePrioritiesFromMap(userIdAndRoomRolePrioritiesMap);

await Rooms.markRolePrioritesCreatedForRoom(rid);
await Rooms.markRolePrioritesCreatedForRoom(rid, SYNC_VERSION);
};
66 changes: 34 additions & 32 deletions apps/meteor/client/views/hooks/useMemberList.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,22 @@ describe('useMembersList', () => {

fakeMembersPage1 = {
offset: 0,
count: 2,
total: 4,
count: 3,
total: 5,
members: [
{ _id: 'user1', username: 'alex', roles: ['owner'], status: UserStatus.ONLINE },
{ _id: 'user2', username: 'john', roles: ['moderator'], status: UserStatus.OFFLINE },
{ _id: 'user2', username: 'bob', roles: ['leader'], status: UserStatus.OFFLINE },
{ _id: 'user3', username: 'john', roles: ['moderator'], status: UserStatus.OFFLINE },
],
};

fakeMembersPage2 = {
offset: 2,
offset: 3,
count: 2,
total: 4,
total: 5,
members: [
{ _id: 'user3', username: 'chris', roles: [], status: UserStatus.ONLINE },
{ _id: 'user4', username: 'zoe', roles: [], status: UserStatus.OFFLINE },
{ _id: 'user4', username: 'chris', roles: [], status: UserStatus.ONLINE },
{ _id: 'user5', username: 'zoe', roles: [], status: UserStatus.OFFLINE },
],
};

Expand All @@ -80,7 +81,7 @@ describe('useMembersList', () => {
useMembersList({
rid: 'room123',
type: 'all',
limit: 2,
limit: 3,
debouncedText: '',
roomType: 'c',
}),
Expand All @@ -102,7 +103,7 @@ describe('useMembersList', () => {
useMembersList({
rid: 'room123',
type: 'all',
limit: 2,
limit: 3,
debouncedText: '',
roomType: 'p',
}),
Expand All @@ -125,7 +126,7 @@ describe('useMembersList', () => {
useMembersList({
rid: 'directRoomId',
type: 'all',
limit: 2,
limit: 3,
debouncedText: '',
roomType: 'd',
}),
Expand All @@ -146,7 +147,7 @@ describe('useMembersList', () => {
useMembersList({
rid: 'room123',
type: 'all',
limit: 2,
limit: 3,
debouncedText: '',
roomType: 'c',
}),
Expand All @@ -157,22 +158,22 @@ describe('useMembersList', () => {
if (offset === 0) {
return fakeMembersPage1 as any;
}
if (offset === 2) {
if (offset === 3) {
return fakeMembersPage2 as any;
}
return {
members: [],
offset,
count: 2,
total: 4,
count: 0,
total: 5,
};
})
.build(),
},
);

await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current.data?.pages[0].members).toHaveLength(2);
expect(result.current.data?.pages[0].members).toHaveLength(3);

await act(async () => {
await result.current.fetchNextPage();
Expand All @@ -194,7 +195,7 @@ describe('useMembersList', () => {
useMembersList({
rid: 'room123',
type: 'all',
limit: 2,
limit: 3,
debouncedText: '',
roomType: 'c',
}),
Expand Down Expand Up @@ -224,7 +225,7 @@ describe('useMembersList', () => {
useMembersList({
rid: 'room123',
type: 'all',
limit: 2,
limit: 3,
debouncedText: '',
roomType: 'c',
}),
Expand All @@ -233,7 +234,7 @@ describe('useMembersList', () => {

await waitFor(() => expect(result.current.isLoading).toBe(false));
let user2 = result.current.data?.pages[0].members.find((m) => m._id === 'user2') as RoomMember;
expect(user2?.roles).toEqual(['moderator']);
expect(user2?.roles).toEqual(['leader']);

// Simulate a roles-change event "added" for user2 -> 'owner'
await act(async () => {
Expand All @@ -248,20 +249,21 @@ describe('useMembersList', () => {
user2 = result.current.data?.pages[0].members.find((m) => m._id === 'user2') as RoomMember;

await waitFor(() => expect(user2?.roles).toContain('owner'));
await waitFor(() => expect(user2?.roles).toContain('moderator'));
await waitFor(() => expect(user2?.roles).toContain('leader'));
});

it('sorts members list cache by "roles > status > username/name" logic on roles-change', async () => {
const customPage = {
offset: 0,
count: 5,
total: 5,
count: 6,
total: 6,
members: [
{ _id: 'u1', username: 'michael', roles: ['owner'], status: UserStatus.OFFLINE },
{ _id: 'u2', username: 'karl', roles: ['moderator'], status: UserStatus.ONLINE },
{ _id: 'u3', username: 'bob', roles: ['moderator'], status: UserStatus.OFFLINE },
{ _id: 'u4', username: 'alex', roles: [], status: UserStatus.OFFLINE },
{ _id: 'u5', username: 'john', roles: [], status: UserStatus.ONLINE },
{ _id: 'u1', username: 'mark', roles: ['owner'], status: UserStatus.OFFLINE },
{ _id: 'u2', username: 'michael', roles: ['leader'], status: UserStatus.OFFLINE },
{ _id: 'u3', username: 'karl', roles: ['moderator'], status: UserStatus.ONLINE },
{ _id: 'u4', username: 'bob', roles: ['moderator'], status: UserStatus.OFFLINE },
{ _id: 'u5', username: 'alex', roles: [], status: UserStatus.OFFLINE },
{ _id: 'u6', username: 'john', roles: [], status: UserStatus.ONLINE },
],
};

Expand Down Expand Up @@ -291,23 +293,23 @@ describe('useMembersList', () => {
await waitFor(() => expect(result.current.isLoading).toBe(false));

let memberIds = result.current.data?.pages[0].members.map((m) => m._id);
expect(memberIds).toEqual(['u1', 'u2', 'u3', 'u4', 'u5']);
expect(memberIds).toEqual(['u1', 'u2', 'u3', 'u4', 'u5', 'u6']);

// Simulate giving user 'alex/u4' the "moderator" role.
// That should push 'alex/u4' to the third of the sorted list -
// after michael/u1 (since owner), after karl/u2 (since online) and before bob/u3 (since sort by username).
// Simulate giving user 'alex/u5' the "moderator" role.
// That should push 'alex/u5' to the third of the sorted list -
// after mark/u1 and michael/u2 (since owner), after karl/u3 (since online) and before bob/u4 (since sort by username).
act(() => {
rolesChangeCallback?.({
type: 'added',
scope: 'room123',
u: { _id: 'u4' },
u: { _id: 'u5' },
_id: 'moderator',
});
});

await waitFor(() => {
memberIds = result.current.data?.pages[0].members.map((m) => m._id);
expect(memberIds).toEqual(['u1', 'u2', 'u4', 'u3', 'u5']);
expect(memberIds).toEqual(['u1', 'u2', 'u3', 'u5', 'u4', 'u6']);
});
});

Expand Down
24 changes: 10 additions & 14 deletions apps/meteor/client/views/hooks/useMembersList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { InfiniteData, QueryClient } from '@tanstack/react-query';
import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';

import { calculateRoomRolePriorityFromRoles } from '../../../lib/roles/calculateRoomRolePriorityFromRoles';

type MembersListOptions = {
rid: string;
type: 'all' | 'online';
Expand All @@ -23,20 +25,14 @@ export type RoomMember = Pick<IUser, 'username' | '_id' | 'name' | 'status' | 'f
type MembersListPage = { members: RoomMember[]; count: number; total: number; offset: number };

const getSortedMembers = (members: RoomMember[], useRealName = false) => {
return members.sort((a, b) => {
const aRoles = a.roles ?? [];
const bRoles = b.roles ?? [];
const isOwnerA = aRoles.includes('owner');
const isOwnerB = bRoles.includes('owner');
const isModeratorA = aRoles.includes('moderator');
const isModeratorB = bRoles.includes('moderator');

if (isOwnerA !== isOwnerB) {
return isOwnerA ? -1 : 1;
}

if (isModeratorA !== isModeratorB && !isOwnerA) {
return isModeratorA ? -1 : 1;
const membersWithRolePriority: (RoomMember & { rolePriority: number })[] = members.map((member) => ({
...member,
rolePriority: calculateRoomRolePriorityFromRoles(member.roles ?? []),
}));

return membersWithRolePriority.sort((a, b) => {
if (a.rolePriority !== b.rolePriority) {
return a.rolePriority - b.rolePriority;
}

if (a.status !== b.status) {
Expand Down
82 changes: 0 additions & 82 deletions apps/meteor/client/views/room/body/LeaderBar.tsx

This file was deleted.

Loading
Loading