Skip to content

Commit

Permalink
Prioritize @ autocomplete results based on recency and thread activity (
Browse files Browse the repository at this point in the history
#7506)

* changed autocomplete results on recency

* fixed issues

* fixed imports, moved get fucn to queries folder

* fixed en.json file

* fixed lint promblems

---------

Co-authored-by: Ilia Polozov <[email protected]>
  • Loading branch information
nixusUM and androidonza authored May 6, 2024
1 parent 8799f2b commit 105073c
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 39 deletions.
78 changes: 40 additions & 38 deletions app/components/autocomplete/at_mention/at_mention.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import {AT_MENTION_REGEX, AT_MENTION_SEARCH_REGEX} from '@constants/autocomplete
import {useServerUrl} from '@context/server';
import DatabaseManager from '@database/manager';
import {t} from '@i18n';
import {queryAllUsers} from '@queries/servers/user';
import {queryAllUsers, getUsersFromDMSorted} from '@queries/servers/user';
import {hasTrailingSpaces} from '@utils/helpers';

import type GroupModel from '@typings/database/models/servers/group';
import type UserModel from '@typings/database/models/servers/user';

const SECTION_KEY_TEAM_MEMBERS = 'teamMembers';
const SECTION_KEY_IN_CHANNEL = 'inChannel';
const SECTION_KEY_OUT_OF_CHANNEL = 'outChannel';
const SECTION_KEY_SPECIAL = 'special';
const SECTION_KEY_GROUPS = 'groups';

Expand Down Expand Up @@ -96,7 +95,7 @@ const filterResults = (users: Array<UserModel | UserProfile>, term: string) => {
});
};

const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersInChannel: Array<UserProfile | UserModel>, usersOutOfChannel: Array<UserProfile | UserModel>, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => {
const makeSections = (teamMembers: Array<UserProfile | UserModel>, users: Array<UserProfile | UserModel>, groups: GroupModel[], showSpecialMentions: boolean, isLocal = false, isSearch = false) => {
const newSections: UserMentionSections = [];

if (isSearch) {
Expand Down Expand Up @@ -136,11 +135,11 @@ const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersInChanne
});
}
} else {
if (usersInChannel.length) {
if (users.length) {
newSections.push({
id: t('suggestion.mention.members'),
defaultMessage: 'Channel Members',
data: usersInChannel,
id: t('suggestion.mention.users'),
defaultMessage: 'Users',
data: users,
key: SECTION_KEY_IN_CHANNEL,
});
}
Expand All @@ -162,15 +161,6 @@ const makeSections = (teamMembers: Array<UserProfile | UserModel>, usersInChanne
key: SECTION_KEY_SPECIAL,
});
}

if (usersOutOfChannel.length) {
newSections.push({
id: t('suggestion.mention.nonmembers'),
defaultMessage: 'Not in Channel',
data: usersOutOfChannel,
key: SECTION_KEY_OUT_OF_CHANNEL,
});
}
}
return newSections;
};
Expand Down Expand Up @@ -248,8 +238,7 @@ const AtMention = ({
const serverUrl = useServerUrl();

const [sections, setSections] = useState<UserMentionSections>(emptySectionList);
const [usersInChannel, setUsersInChannel] = useState<Array<UserProfile | UserModel>>(emptyUserlList);
const [usersOutOfChannel, setUsersOutOfChannel] = useState<Array<UserProfile | UserModel>>(emptyUserlList);
const [users, setUsers] = useState<Array<UserProfile | UserModel>>(emptyUserlList);
const [groups, setGroups] = useState<GroupModel[]>(emptyGroupList);
const [loading, setLoading] = useState(false);
const [noResultsTerm, setNoResultsTerm] = useState<string|null>(null);
Expand Down Expand Up @@ -289,30 +278,43 @@ const AtMention = ({
const filteredUsers = filterResults(fallbackUsers, term);
setFilteredLocalUsers(filteredUsers.length ? filteredUsers : emptyUserlList);
} else if (receivedUsers) {
if (hasTrailingSpaces(term)) {
const filteredReceivedUsers = filterResults(receivedUsers.users, term);
const filteredReceivedOutOfChannelUsers = filterResults(receivedUsers.out_of_channel || [], term);

setUsersInChannel(filteredReceivedUsers.length ? filteredReceivedUsers : emptyUserlList);
setUsersOutOfChannel(filteredReceivedOutOfChannelUsers.length ? filteredReceivedOutOfChannelUsers : emptyUserlList);
} else {
setUsersInChannel(receivedUsers.users.length ? receivedUsers.users : emptyUserlList);
setUsersOutOfChannel(receivedUsers.out_of_channel?.length ? receivedUsers.out_of_channel : emptyUserlList);
}
sortRecievedUsers(sUrl, term, receivedUsers?.users, receivedUsers?.out_of_channel);
}

setLoading(false);
}, 200), []);

const teamMembers = useMemo(
() => [...usersInChannel, ...usersOutOfChannel],
[usersInChannel, usersOutOfChannel],
);
async function sortRecievedUsers(sUrl: string, term: string, receivedUsers: UserProfile[], outOfChannel: UserProfile[] | undefined) {
const database = DatabaseManager.serverDatabases[sUrl]?.database;
if (!database) {
return;
}
const memberIds = receivedUsers.map((e) => e.id);
const sortedMembers: Array<UserProfile | UserModel> = await getUsersFromDMSorted(database, memberIds);
const sortedMembersId = new Set<string>(sortedMembers.map((e) => e.id));

const membersNoDm = receivedUsers.filter((u) => !sortedMembersId.has(u.id));
sortedMembers.push(...membersNoDm);

if (outOfChannel?.length) {
const outChannelMemberIds = outOfChannel.map((e) => e.id);
const outSortedMembers = await getUsersFromDMSorted(database, outChannelMemberIds);
sortedMembers.push(...outSortedMembers);
}

if (hasTrailingSpaces(term)) {
const filteredReceivedUsers = filterResults(sortedMembers, term);
const slicedArray = filteredReceivedUsers.slice(0, 20);
setUsers(slicedArray.length ? slicedArray : emptyUserlList);
} else {
const slicedArray = sortedMembers.slice(0, 20);
setUsers(slicedArray.length ? slicedArray : emptyUserlList);
}
}

const matchTerm = getMatchTermForAtMention(value.substring(0, localCursorPosition), isSearch);
const resetState = () => {
setUsersInChannel(emptyUserlList);
setUsersOutOfChannel(emptyUserlList);
setUsers(emptyUserlList);
setGroups(emptyGroupList);
setFilteredLocalUsers(emptyUserlList);
setSections(emptySectionList);
Expand Down Expand Up @@ -430,12 +432,12 @@ const AtMention = ({
return;
}
const showSpecialMentions = useChannelMentions && matchTerm != null && checkSpecialMentions(matchTerm);
const buildMemberSection = isSearch || (!channelId && teamMembers.length > 0);
const buildMemberSection = isSearch || (!channelId && users.length > 0);
let newSections;
if (useLocal) {
newSections = makeSections(filteredLocalUsers, [], [], groups, showSpecialMentions, true, buildMemberSection);
newSections = makeSections(filteredLocalUsers, [], groups, showSpecialMentions, true, buildMemberSection);
} else {
newSections = makeSections(teamMembers, usersInChannel, usersOutOfChannel, groups, showSpecialMentions, buildMemberSection);
newSections = makeSections(users, users, groups, showSpecialMentions, buildMemberSection);
}
const nSections = newSections.length;

Expand All @@ -448,7 +450,7 @@ const AtMention = ({
}
setSections(nSections ? newSections : emptySectionList);
onShowingChange(Boolean(nSections));
}, [!useLocal && usersInChannel, !useLocal && usersOutOfChannel, teamMembers, groups, loading, channelId, useLocal && filteredLocalUsers]);
}, [!useLocal && users, users, groups, loading, channelId, useLocal && filteredLocalUsers]);

if (sections.length === 0 || noResultsTerm != null) {
// If we are not in an active state or the mention has been completed return null so nothing is rendered
Expand Down
17 changes: 16 additions & 1 deletion app/queries/servers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Database, Q} from '@nozbe/watermelondb';
import {combineLatest, of as of$} from 'rxjs';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';

import {General} from '@constants';
import {MM_TABLES} from '@constants/database';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {sanitizeLikeString} from '@helpers/database';
Expand All @@ -17,7 +18,7 @@ import type ChannelMembershipModel from '@typings/database/models/servers/channe
import type TeamMembershipModel from '@typings/database/models/servers/team_membership';
import type UserModel from '@typings/database/models/servers/user';

const {SERVER: {CHANNEL_MEMBERSHIP, USER, TEAM_MEMBERSHIP}} = MM_TABLES;
const {SERVER: {CHANNEL_MEMBERSHIP, USER, TEAM_MEMBERSHIP, CHANNEL, MY_CHANNEL}} = MM_TABLES;
export const getUserById = async (database: Database, userId: string) => {
try {
const userRecord = (await database.get<UserModel>(USER).find(userId));
Expand Down Expand Up @@ -135,3 +136,17 @@ export const observeDeactivatedUsers = (database: Database) => {
}),
);
};

export const getUsersFromDMSorted = async (database: Database, memberIds: string[]) => {
try {
return database.get<UserModel>(USER).query(
Q.unsafeSqlQuery(`SELECT DISTINCT u.* FROM User u
INNER JOIN ${CHANNEL_MEMBERSHIP} cm ON cm.user_id=u.id
INNER JOIN ${CHANNEL} c ON c.id=cm.id AND c.type='${General.DM_CHANNEL}'
INNER JOIN ${MY_CHANNEL} my
WHERE cm.user_id IN (${`'${memberIds.join("','")}'`})
ORDER BY my.last_viewed_at DESC`)).fetch();
} catch (error) {
return [];
}
};
1 change: 1 addition & 0 deletions assets/base/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,7 @@
"suggestion.mention.here": "Notifies everyone online in this channel",
"suggestion.mention.members": "Channel Members",
"suggestion.mention.morechannels": "Other Channels",
"suggestion.mention.users": "Users",
"suggestion.mention.nonmembers": "Not in Channel",
"suggestion.mention.special": "Special Mentions",
"suggestion.search.direct": "Direct Messages",
Expand Down

0 comments on commit 105073c

Please sign in to comment.