From e2612eb455cd3717e048b49ea42c0ff75e76ffa6 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Tue, 8 Mar 2022 11:24:21 -0300 Subject: [PATCH] Chore: Migrate search and localSearch methods to Typescript (#3751) * chore: migrate search to typescript * fix types * fix type Co-authored-by: Diego Mello --- app/definitions/ISearch.ts | 20 ++++ app/definitions/index.ts | 1 + app/lib/rocketchat/methods/search.ts | 98 +++++++++++++++++++ app/lib/rocketchat/rocketchat.js | 89 +---------------- .../CreateDiscussionView/SelectChannel.tsx | 16 +-- app/views/NewMessageView.tsx | 18 +--- 6 files changed, 132 insertions(+), 110 deletions(-) create mode 100644 app/definitions/ISearch.ts create mode 100644 app/lib/rocketchat/methods/search.ts diff --git a/app/definitions/ISearch.ts b/app/definitions/ISearch.ts new file mode 100644 index 000000000..e4ef9de62 --- /dev/null +++ b/app/definitions/ISearch.ts @@ -0,0 +1,20 @@ +import { ILastMessage } from './IMessage'; + +export interface ISearchLocal { + avatarETag: string; + rid: string; + name: string; + t: string; + fname: string; + encrypted: boolean | null; + lastMessage?: ILastMessage; +} + +export interface ISearch extends ISearchLocal { + // return only from api search + _id: string; + status?: string; + username: string; + outside?: boolean; + search?: boolean; +} diff --git a/app/definitions/index.ts b/app/definitions/index.ts index fca277b2b..2d55e2139 100644 --- a/app/definitions/index.ts +++ b/app/definitions/index.ts @@ -24,6 +24,7 @@ export * from './IServerHistory'; export * from './IRocketChat'; export * from './ICertificate'; export * from './IUrl'; +export * from './ISearch'; export interface IBaseScreen, S extends string> { navigation: StackNavigationProp; diff --git a/app/lib/rocketchat/methods/search.ts b/app/lib/rocketchat/methods/search.ts new file mode 100644 index 000000000..136dce0b6 --- /dev/null +++ b/app/lib/rocketchat/methods/search.ts @@ -0,0 +1,98 @@ +import { Q } from '@nozbe/watermelondb'; + +import { sanitizeLikeString } from '../../database/utils'; +import database from '../../database/index'; +import { spotlight } from '../services/restApi'; +import isGroupChat from './isGroupChat'; +import { ISearch, ISearchLocal, SubscriptionType } from '../../../definitions'; + +let debounce: null | ((reason: string) => void) = null; + +export const localSearch = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<(ISearch | ISearchLocal)[]> => { + const searchText = text.trim(); + const db = database.active; + const likeString = sanitizeLikeString(searchText); + let subscriptions = await db + .get('subscriptions') + .query( + Q.or(Q.where('name', Q.like(`%${likeString}%`)), Q.where('fname', Q.like(`%${likeString}%`))), + Q.experimentalSortBy('room_updated_at', Q.desc) + ) + .fetch(); + + if (filterUsers && !filterRooms) { + subscriptions = subscriptions.filter(item => item.t === 'd' && !isGroupChat(item)); + } else if (!filterUsers && filterRooms) { + subscriptions = subscriptions.filter(item => item.t !== 'd' || isGroupChat(item)); + } + + const sliceSubscriptions = subscriptions.slice(0, 7); + + const search = sliceSubscriptions.map(sub => ({ + rid: sub.rid, + name: sub.name, + fname: sub?.fname || '', + avatarETag: sub?.avatarETag || '', + t: sub.t, + encrypted: sub?.encrypted || null, + lastMessage: sub.lastMessage, + ...(sub.teamId && { teamId: sub.teamId }) + })) as (ISearch | ISearchLocal)[]; + + return search; +}; + +export const search = async ({ text = '', filterUsers = true, filterRooms = true }): Promise<(ISearch | ISearchLocal)[]> => { + const searchText = text.trim(); + + if (debounce) { + debounce('cancel'); + } + + const localSearchData = await localSearch({ text, filterUsers, filterRooms }); + const usernames = localSearchData.map(sub => sub.name); + + const data = localSearchData as (ISearch | ISearchLocal)[]; + + try { + if (localSearchData.length < 7) { + const { users, rooms } = (await Promise.race([ + spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }), + 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 + .forEach(user => { + data.push({ + ...user, + rid: user.username, + name: user.username, + t: SubscriptionType.DIRECT, + search: true + }); + }); + } + if (filterRooms) { + rooms.forEach(room => { + // Check if it exists on local database + const index = data.findIndex(item => item.rid === room._id); + if (index === -1) { + data.push({ + ...room, + rid: room._id, + search: true + }); + } + }); + } + } + debounce = null; + return data; + } catch (e) { + console.warn(e); + return data; + } +}; diff --git a/app/lib/rocketchat/rocketchat.js b/app/lib/rocketchat/rocketchat.js index 3035af71b..d48d49638 100644 --- a/app/lib/rocketchat/rocketchat.js +++ b/app/lib/rocketchat/rocketchat.js @@ -62,6 +62,7 @@ import getRoom from './methods/getRoom'; import isGroupChat from './methods/isGroupChat'; import roomTypeToApiType from './methods/roomTypeToApiType'; import getUserInfo from './services/getUserInfo'; +import * as search from './methods/search'; // Services import sdk from './services/sdk'; import toggleFavorite from './services/toggleFavorite'; @@ -83,6 +84,7 @@ const RocketChat = { CURRENT_SERVER, CERTIFICATE_KEY, ...restAPis, + ...search, callJitsi, callJitsiWithoutServer, async subscribeRooms() { @@ -628,93 +630,6 @@ const RocketChat = { getRooms, readMessages, resendMessage, - - async localSearch({ text, filterUsers = true, filterRooms = true }) { - const searchText = text.trim(); - const db = database.active; - const likeString = sanitizeLikeString(searchText); - let data = await db - .get('subscriptions') - .query( - Q.or(Q.where('name', Q.like(`%${likeString}%`)), Q.where('fname', Q.like(`%${likeString}%`))), - Q.experimentalSortBy('room_updated_at', Q.desc) - ) - .fetch(); - - if (filterUsers && !filterRooms) { - data = data.filter(item => item.t === 'd' && !RocketChat.isGroupChat(item)); - } else if (!filterUsers && filterRooms) { - data = data.filter(item => item.t !== 'd' || RocketChat.isGroupChat(item)); - } - - data = data.slice(0, 7); - - data = data.map(sub => ({ - rid: sub.rid, - name: sub.name, - fname: sub.fname, - avatarETag: sub.avatarETag, - t: sub.t, - encrypted: sub.encrypted, - lastMessage: sub.lastMessage, - ...(sub.teamId && { teamId: sub.teamId }) - })); - - return data; - }, - - async search({ text, filterUsers = true, filterRooms = true }) { - const searchText = text.trim(); - - if (this.oldPromise) { - this.oldPromise('cancel'); - } - - const data = await this.localSearch({ text, filterUsers, filterRooms }); - - const usernames = data.map(sub => sub.name); - try { - if (data.length < 7) { - const { users, rooms } = await Promise.race([ - RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }), - new Promise((resolve, reject) => (this.oldPromise = reject)) - ]); - 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 - .forEach(user => { - data.push({ - ...user, - rid: user.username, - name: user.username, - t: 'd', - search: true - }); - }); - } - if (filterRooms) { - rooms.forEach(room => { - // Check if it exists on local database - const index = data.findIndex(item => item.rid === room._id); - if (index === -1) { - data.push({ - rid: room._id, - ...room, - search: true - }); - } - }); - } - } - delete this.oldPromise; - return data; - } catch (e) { - console.warn(e); - return data; - // return []; - } - }, createGroupChat() { const { users } = reduxStore.getState().selectedUsers; const usernames = users.map(u => u.name).join(','); diff --git a/app/views/CreateDiscussionView/SelectChannel.tsx b/app/views/CreateDiscussionView/SelectChannel.tsx index d779dc3af..f74c7d973 100644 --- a/app/views/CreateDiscussionView/SelectChannel.tsx +++ b/app/views/CreateDiscussionView/SelectChannel.tsx @@ -1,15 +1,15 @@ import React, { useState } from 'react'; import { Text } from 'react-native'; -import debounce from '../../utils/debounce'; -import { avatarURL } from '../../utils/avatar'; -import RocketChat from '../../lib/rocketchat'; -import I18n from '../../i18n'; -import { MultiSelect } from '../../containers/UIKit/MultiSelect'; import { themes } from '../../constants/colors'; -import { TSubscriptionModel } from '../../definitions/ISubscription'; -import styles from './styles'; +import { MultiSelect } from '../../containers/UIKit/MultiSelect'; +import { ISearchLocal } from '../../definitions'; +import I18n from '../../i18n'; +import RocketChat from '../../lib/rocketchat'; +import { avatarURL } from '../../utils/avatar'; +import debounce from '../../utils/debounce'; import { ICreateDiscussionViewSelectChannel } from './interfaces'; +import styles from './styles'; const SelectChannel = ({ server, @@ -21,7 +21,7 @@ const SelectChannel = ({ serverVersion, theme }: ICreateDiscussionViewSelectChannel): JSX.Element => { - const [channels, setChannels] = useState([]); + const [channels, setChannels] = useState([]); const getChannels = debounce(async (keyword = '') => { try { diff --git a/app/views/NewMessageView.tsx b/app/views/NewMessageView.tsx index 1cc84a21e..c135e1b98 100644 --- a/app/views/NewMessageView.tsx +++ b/app/views/NewMessageView.tsx @@ -12,7 +12,7 @@ import * as List from '../containers/List'; import SafeAreaView from '../containers/SafeAreaView'; import SearchBox from '../containers/SearchBox'; import StatusBar from '../containers/StatusBar'; -import { IApplicationState, IBaseScreen, TSubscriptionModel } from '../definitions'; +import { IApplicationState, IBaseScreen, ISearch, TSubscriptionModel } from '../definitions'; import I18n from '../i18n'; import database from '../lib/database'; import { CustomIcon } from '../lib/Icons'; @@ -55,18 +55,6 @@ interface IButton { first?: boolean; } -interface ISearch { - _id: string; - status: string; - username: string; - avatarETag: string; - outside: boolean; - rid: string; - name: string; - t: string; - search: boolean; -} - interface INewMessageViewState { search: (ISearch | TSubscriptionModel)[]; chats: TSubscriptionModel[]; @@ -149,7 +137,7 @@ class NewMessageView extends React.Component { - const result: ISearch[] | TSubscriptionModel[] = await RocketChat.search({ text, filterRooms: false }); + const result = (await RocketChat.search({ text, filterRooms: false })) as ISearch[]; this.setState({ search: result }); @@ -313,7 +301,7 @@ class NewMessageView extends React.Component 0 ? search : chats} extraData={this.state} - keyExtractor={item => item._id} + keyExtractor={item => item._id || item.rid} ListHeaderComponent={this.renderHeader} renderItem={this.renderItem} ItemSeparatorComponent={List.Separator}