diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 052ecedd38a..8e397021b63 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -437,7 +437,6 @@ export default class InviteDialog extends React.PureComponent { - console.log(m); this.spaceMemberIds.push(m.userId); }); // Verji end @@ -535,8 +534,12 @@ export default class InviteDialog extends React.PureComponent excludedTargetIds.add(id)); } - // VERJI added param activeSpaceMembers - used to fileter the recents based on membership in space + // VERJI added param activeSpaceMembers - used to filter the recents based on membership in space public static buildRecents(excludedTargetIds: Set, activeSpaceMembers: string[]): Result[] { + // Verji - If we don't want to see the Recents-suggestions(featureflag), we just return an empty array + if(!SettingsStore.getValue(UIFeature.ShowRecentsInSuggestions)){ + return [] as Result[] + } const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room // Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the @@ -618,10 +621,13 @@ export default class InviteDialog extends React.PureComponent): { userId: string; user: Member }[] { const cli = MatrixClientPeg.safeGet(); const activityScores = buildActivityScores(cli); - const memberScores = buildMemberScores(cli); - const memberComparator = compareMembers(activityScores, memberScores); + let memberScores = {} as { [userId: string]: { member: RoomMember; score: number; numRooms: number } }; + if (SettingsStore.getValue(UIFeature.ShowRoomMembersInSuggestions)) { + memberScores = buildMemberScores(cli); + } + const memberComparator = compareMembers(activityScores, memberScores); return Object.values(memberScores) .map(({ member }) => member) .filter((member) => !excludedTargetIds.has(member.userId)) @@ -657,7 +663,6 @@ export default class InviteDialog extends React.PureComponent { this.setState({ busy: false }); - if (r.results.find((e) => e.user_id == this.state.filterText.trim())) { foundUser = true; } @@ -674,12 +679,21 @@ export default class InviteDialog extends React.PureComponent m.userId === u.userId) && !priorityAdditionalMembers.some((m) => m.userId === u.userId) && !otherAdditionalMembers.some((m) => m.userId === u.userId) + //HERE ); }; @@ -1384,6 +1400,7 @@ export default class InviteDialog extends React.PureComponent = ({ initialText = "", initialFilter = n userResults.push(result); } } - addUserResults(findVisibleRoomMembers(visibleRooms, cli), false); + if (SettingsStore.getValue(UIFeature.ShowRoomMembersInSuggestions)) { + addUserResults(findVisibleRoomMembers(visibleRooms, cli), false); + } addUserResults(userDirectorySearchResults, true); if (profile) { addUserResults([new DirectoryMember(profile)], true); @@ -428,7 +430,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", initialFilter = n if (trimmedQuery) { const lcQuery = trimmedQuery.toLowerCase(); const normalizedQuery = normalize(trimmedQuery); - possibleResults.forEach((entry) => { if (isRoomResult(entry)) { // If the room is a DM with a user that is part of the user directory search results, diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 94aab9222c5..2f73ff1cae3 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1511,6 +1511,14 @@ export const SETTINGS: { [setting: string]: ISetting } = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + [UIFeature.ShowRoomMembersInSuggestions]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, + [UIFeature.ShowRecentsInSuggestions]: { + supportedLevels: LEVELS_UI_FEATURE, + default: true, + }, // Electron-specific settings, they are stored by Electron and set/read over an IPC. // We store them over there are they are necessary to know before the renderer process launches. diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts index 540932d87e3..082e158b3a0 100644 --- a/src/settings/UIFeature.ts +++ b/src/settings/UIFeature.ts @@ -102,6 +102,8 @@ export const enum UIFeature { HelpShowMatrixDisclosurePolicyAndLinks = "UIFeature.helpShowMatrixDisclosurePolicyAndLinks", ShowInviteToSpaceFromPeoplePlus = "UIFeature.showInviteToSpaceFromPeoplePlus", SettingShowMessageSearch = "UIFeature.settingShowMessageSearch", + ShowRoomMembersInSuggestions = "UIFeature.showRoomMembersInSuggestions", + ShowRecentsInSuggestions = "UIFeature.showRecentsInSuggestions", } export enum UIComponent { diff --git a/src/utils/SortMembers.ts b/src/utils/SortMembers.ts index 413f7971b2c..0c59ac8f452 100644 --- a/src/utils/SortMembers.ts +++ b/src/utils/SortMembers.ts @@ -20,6 +20,8 @@ import { KnownMembership } from "matrix-js-sdk/src/types"; import { Member } from "./direct-messages"; import DMRoomMap from "./DMRoomMap"; +import SettingsStore from "../settings/SettingsStore"; +import { UIFeature } from "../settings/UIFeature"; export const compareMembers = ( @@ -100,12 +102,29 @@ interface IMemberScore { } export function buildMemberScores(cli: MatrixClient): { [userId: string]: IMemberScore } { + // VERJI - if feature flag is false, we don't need to calculate and build memberscores, and just return an empty map instead. Suggestions will be populated by searchResults instead + if (!SettingsStore.getValue(UIFeature.ShowRoomMembersInSuggestions)) { + return {} as { [userId: string]: IMemberScore }; + } const maxConsideredMembers = 200; const consideredRooms = joinedRooms(cli).filter((room) => room.getJoinedMemberCount() < maxConsideredMembers); - const memberPeerEntries = consideredRooms.flatMap((room) => - room.getJoinedMembers().map((member) => ({ member, roomSize: room.getJoinedMemberCount() })), - ); + + const memberPeerEntries = consideredRooms.flatMap((room) => { + // VERJI Log the roomId here + console.log("[SortMembers.ts] - Processing room:", room.roomId); + console.log("[SortMembers.ts] - is this a space room?:", room.isSpaceRoom()); + // VERJI - A filter to exclude members from Space Rooms, not really necessary if featureflag showRoomMembersInSuggestions is false, but keeping it in case we want to "fine-tune" suggestions later + if (room.isSpaceRoom()) { + console.log("[SortMembers.ts] - skipping the room", room.roomId); + console.log("[SortMembers.ts] - number of members excluded: ", room.getJoinedMemberCount()); + return []; + } + + return room.getJoinedMembers().map((member) => ({ member, roomSize: room.getJoinedMemberCount() })); + }); + const userMeta = groupBy(memberPeerEntries, ({ member }) => member.userId); + // If the iteratee in mapValues returns undefined that key will be removed from the resultant object return mapValues(userMeta, (roomMemberships) => { if (!roomMemberships.length) return; diff --git a/test/components/views/dialogs/InviteDialog-test.tsx b/test/components/views/dialogs/InviteDialog-test.tsx index 5ca76d9e30e..9ef8515641d 100644 --- a/test/components/views/dialogs/InviteDialog-test.tsx +++ b/test/components/views/dialogs/InviteDialog-test.tsx @@ -43,6 +43,8 @@ import SettingsStore from "../../../../src/settings/SettingsStore"; import Modal from "../../../../src/Modal"; import HomePage from "../../../../src/components/structures/HomePage"; import { UIFeature } from "../../../../src/settings/UIFeature"; +import * as SortMembers from "../../../../src/utils/SortMembers"; +import SpaceStore from "../../../../src/stores/spaces/SpaceStore"; const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken"); jest.mock("../../../../src/IdentityAuthClient", () => @@ -189,7 +191,79 @@ describe("InviteDialog", () => { afterAll(() => { jest.restoreAllMocks(); }); + describe("UIFeature.ShowRoomMembersInSuggestions", () => { + const testUser = { + _userId: "@suggestedMember:verji.app", + displayName: "Suggested Member", + getMxcAvatarUrl: jest.fn().mockReturnValue(aliceProfileInfo.avatar_url), + }; + const mockSpaceMembers = [ + { + userId: testUser._userId, + user: { + _userId: "@suggestedMember:verji.app", + displayName: "Suggested Member", + getMxcAvatarUrl: jest.fn().mockReturnValue(aliceProfileInfo.avatar_url), + }, + }, + ]; + + const memberScores: { [userId: string]: any } = { + [testUser._userId]: { + member: testUser, + score: 0.92, + numRooms: 2, + }, + }; + beforeEach(() => { + // Mock activeSpaceRoom to be an object with getJoinedMembers method + const roomMock: Partial = { + getJoinedMembers: jest.fn().mockReturnValue([mockSpaceMembers /* mock RoomMember[] */]), + }; + const mockBuildMembers = jest.spyOn(SortMembers, "buildMemberScores"); + mockBuildMembers.mockImplementation(() => { + return memberScores; + }); + + // Mock the SpaceStore instance and activeSpaceRoom + jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(roomMock as Room); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it("Should render suggestions when UIFeature.ShowRoomMembersInSuggestions is TRUE(default)", async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => { + if (name == UIFeature.ShowRoomMembersInSuggestions) return true; + }); + + render(); + + expect(screen.queryAllByText("Suggestions").length).toBe(1); + // The "presentation" is used by the rendered tiles of members in the suggestion. + const suggestionTile = screen.getAllByRole("presentation"); + + expect(suggestionTile).toBeTruthy(); + }); + + it("should NOT render suggestions when UIFeature.ShowRoomMembersInSuggestions is FALSE", async () => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => { + if (name == UIFeature.ShowRoomMembersInSuggestions) return false; + }); + + render(); + + expect(screen.queryAllByText("Suggestions").length).toBe(0); + + // The "presentation" is used by the rendered tiles of members in the suggestion. + const suggestionTile = screen.queryByRole("presentation"); + + expect(suggestionTile).toBeFalsy(); + }); + }); it("should label with space name", () => { room.isSpaceRoom = jest.fn().mockReturnValue(true); room.getType = jest.fn().mockReturnValue(RoomType.Space);