[IMPROVE] Spotlight users order (#4527)

* [IMPROVE] Spotlight users order

* minor tweak query

* minor tweak removing query and using regex

* minor tweak

* minor tweak, make ts happy

* fix the ts

* fix lint and type TSearch
This commit is contained in:
Reinaldo Neto 2022-10-31 13:43:47 -03:00 committed by GitHub
parent 2b53898066
commit 57620ff40d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 20 deletions

View File

@ -658,7 +658,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
};
getUsers = debounce(async (keyword: any) => {
let res = await search({ text: keyword, filterRooms: false, filterUsers: true });
const { rid } = this.props;
let res = await search({ text: keyword, filterRooms: false, filterUsers: true, rid });
res = [...this.getFixedMentions(keyword), ...res];
this.setState({ mentions: res, mentionLoading: false });
}, 300);

View File

@ -226,5 +226,8 @@ export const defaultSettings = {
},
Accounts_AllowDeleteOwnAccount: {
type: 'valueAsBoolean'
},
Number_of_users_autocomplete_suggestions: {
type: 'valueAsNumber'
}
} as const;

View File

@ -2,13 +2,16 @@ import { Q } from '@nozbe/watermelondb';
import { sanitizeLikeString } from '../database/utils';
import database from '../database/index';
import { store as reduxStore } from '../store/auxStore';
import { spotlight } from '../services/restApi';
import { ISearch, ISearchLocal, SubscriptionType } from '../../definitions';
import { ISearch, ISearchLocal, IUserMessage, SubscriptionType } from '../../definitions';
import { isGroupChat } from './helpers';
export type TSearch = ISearchLocal | IUserMessage | ISearch;
let debounce: null | ((reason: string) => void) = null;
export const localSearch = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<ISearchLocal[]> => {
export const localSearchSubscription = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<ISearchLocal[]> => {
const searchText = text.trim();
const db = database.active;
const likeString = sanitizeLikeString(searchText);
@ -41,29 +44,60 @@ export const localSearch = async ({ text = '', filterUsers = true, filterRooms =
return search;
};
export const search = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<(ISearch | ISearchLocal)[]> => {
export const localSearchUsersMessageByRid = async ({ text = '', rid = '' }): Promise<IUserMessage[]> => {
const userId = reduxStore.getState().login.user.id;
const numberOfSuggestions = reduxStore.getState().settings.Number_of_users_autocomplete_suggestions as number;
const searchText = text.trim();
const db = database.active;
const likeString = sanitizeLikeString(searchText);
const messages = await db
.get('messages')
.query(
Q.and(Q.where('rid', rid), Q.where('u', Q.notLike(`%${userId}%`)), Q.where('t', null)),
Q.experimentalSortBy('ts', Q.desc),
Q.experimentalTake(50)
)
.fetch();
const regExp = new RegExp(`${likeString}`, 'i');
const users = messages.map(message => message.u);
const usersFromLocal = users
.filter((item1, index) => users.findIndex(item2 => item2._id === item1._id) === index) // Remove duplicated data from response
.filter(user => user?.name?.match(regExp) || user?.username?.match(regExp))
.slice(0, text ? 2 : numberOfSuggestions);
return usersFromLocal;
};
export const search = async ({ text = '', filterUsers = true, filterRooms = true, rid = '' }): Promise<TSearch[]> => {
const searchText = text.trim();
if (debounce) {
debounce('cancel');
}
const localSearchData = await localSearch({ text, filterUsers, filterRooms });
const usernames = localSearchData.map(sub => sub.name);
let localSearchData = [];
if (rid) {
localSearchData = await localSearchUsersMessageByRid({ text, rid });
} else {
localSearchData = await localSearchSubscription({ text, filterUsers, filterRooms });
}
const usernames = localSearchData.map(sub => sub.name as string);
const data = localSearchData as (ISearch | ISearchLocal)[];
const data: TSearch[] = localSearchData;
try {
if (localSearchData.length < 7) {
if (searchText && localSearchData.length < 7) {
const { users, rooms } = (await Promise.race([
spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }),
spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }, rid),
new Promise((resolve, reject) => (debounce = reject))
])) as { users: ISearch[]; rooms: ISearch[] };
if (filterUsers) {
users
.filter((item1, index) => users.findIndex(item2 => item2._id === item1._id) === index) // Remove duplicated data from response
.filter(user => !data.some(sub => user.username === sub.name)) // Make sure to remove users already on local database
.filter(user => !data.some(sub => 'username' in sub && user.username === sub.username)) // Make sure to remove users already on local database
.forEach(user => {
data.push({
...user,
@ -77,7 +111,7 @@ export const search = async ({ text = '', filterUsers = true, filterRooms = true
if (filterRooms) {
rooms.forEach(room => {
// Check if it exists on local database
const index = data.findIndex(item => item.rid === room._id);
const index = data.findIndex(item => 'rid' in item && item.rid === room._id);
if (index === -1) {
data.push({
...room,

View File

@ -89,9 +89,16 @@ export const forgotPassword = (email: string) =>
export const sendConfirmationEmail = (email: string): Promise<{ message: string; success: boolean }> =>
sdk.methodCallWrapper('sendConfirmationEmail', email);
export const spotlight = (search: string, usernames: string[], type: { users: boolean; rooms: boolean }): Promise<ISpotlight> =>
export const spotlight = (
search: string,
usernames: string[],
type: { users: boolean; rooms: boolean },
rid?: string
): Promise<ISpotlight> =>
// RC 0.51.0
sdk.methodCallWrapper('spotlight', search, usernames, type);
rid
? sdk.methodCallWrapper('spotlight', search, usernames, type, rid)
: sdk.methodCallWrapper('spotlight', search, usernames, type);
export const createDirectMessage = (username: string) =>
// RC 0.59.0

View File

@ -7,7 +7,7 @@ import I18n from '../../i18n';
import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl';
import { ICreateDiscussionViewSelectChannel } from './interfaces';
import styles from './styles';
import { localSearch } from '../../lib/methods';
import { localSearchSubscription } from '../../lib/methods';
import { getRoomAvatar, getRoomTitle } from '../../lib/methods/helpers';
import { useTheme } from '../../theme';
@ -25,7 +25,7 @@ const SelectChannel = ({
const getChannels = async (keyword = '') => {
try {
const res = (await localSearch({ text: keyword, filterUsers: false })) as ISearchLocal[];
const res = (await localSearchSubscription({ text: keyword, filterUsers: false })) as ISearchLocal[];
setChannels(res);
return res.map(channel => ({
value: channel,

View File

@ -13,7 +13,6 @@ import * as List from '../../containers/List';
import { sendLoadingEvent } from '../../containers/Loading';
import SafeAreaView from '../../containers/SafeAreaView';
import StatusBar from '../../containers/StatusBar';
import { ISearch, ISearchLocal } from '../../definitions';
import I18n from '../../i18n';
import database from '../../lib/database';
import UserItem from '../../containers/UserItem';
@ -23,7 +22,7 @@ import { ChatsStackParamList } from '../../stacks/types';
import { useTheme } from '../../theme';
import { showErrorAlert } from '../../lib/methods/helpers/info';
import log, { events, logEvent } from '../../lib/methods/helpers/log';
import { search as searchMethod } from '../../lib/methods';
import { search as searchMethod, TSearch } from '../../lib/methods';
import { isGroupChat as isGroupChatMethod } from '../../lib/methods/helpers';
import { useAppSelector } from '../../lib/hooks';
import Header from './Header';
@ -31,11 +30,9 @@ import Header from './Header';
type TRoute = RouteProp<ChatsStackParamList, 'SelectedUsersView'>;
type TNavigation = StackNavigationProp<ChatsStackParamList, 'SelectedUsersView'>;
type TSearchItem = ISearch | ISearchLocal;
const SelectedUsersView = () => {
const [chats, setChats] = useState<ISelectedUser[]>([]);
const [search, setSearch] = useState<TSearchItem[]>([]);
const [search, setSearch] = useState<TSearch[]>([]);
const { maxUsers, showButton, title, buttonText, nextAction } = useRoute<TRoute>().params;
const navigation = useNavigation<TNavigation>();