Merge branch 'develop' into chore.dehydrate-login-methods-from-rocketchatjs

This commit is contained in:
Gerzon Z 2022-03-08 11:00:04 -04:00
commit 0949b1d70e
54 changed files with 747 additions and 486 deletions

1
.gitignore vendored
View File

@ -64,5 +64,6 @@ artifacts
.vscode/ .vscode/
e2e/docker/rc_test_env/docker-compose.yml e2e/docker/rc_test_env/docker-compose.yml
e2e/docker/data/db e2e/docker/data/db
e2e/e2e_account.js
*.p8 *.p8

View File

@ -605,7 +605,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
getCannedResponses = debounce(async (text?: string) => { getCannedResponses = debounce(async (text?: string) => {
const res = await RocketChat.getListCannedResponse({ text }); const res = await RocketChat.getListCannedResponse({ text });
this.setState({ mentions: res?.cannedResponses || [], mentionLoading: false }); this.setState({ mentions: res.success ? res.cannedResponses : [], mentionLoading: false });
}, 500); }, 500);
focus = () => { focus = () => {

View File

@ -44,22 +44,22 @@ export type E2EType = 'pending' | 'done';
export interface ILastMessage { export interface ILastMessage {
_id: string; _id: string;
rid: string; rid: string;
tshow: boolean; tshow?: boolean;
t: MessageType; t?: MessageType;
tmid: string; tmid?: string;
msg: string; msg?: string;
e2e: E2EType; e2e?: E2EType;
ts: Date; ts: string | Date;
u: IUserMessage; u: IUserMessage;
_updatedAt: Date; _updatedAt: string | Date;
urls: string[]; urls?: IUrlFromServer[];
mentions: IUserMention[]; mentions?: IUserMention[];
channels: IUserChannel[]; channels?: IUserChannel[];
md: MarkdownAST; md?: MarkdownAST;
attachments: IAttachment[]; attachments?: IAttachment[];
reactions: IReaction[]; reactions?: IReaction[];
unread: boolean; unread?: boolean;
status: boolean; status?: number;
} }
interface IMessageFile { interface IMessageFile {
@ -68,38 +68,21 @@ interface IMessageFile {
type: string; type: string;
} }
interface IMessageAttachment {
ts: string;
title: string;
title_link: string;
title_link_download: true;
image_dimensions: {
width: number;
height: number;
};
image_preview: string;
image_url: string;
image_type: string;
image_size: number;
type: string;
description: string;
}
export interface IMessageFromServer { export interface IMessageFromServer {
_id: string; _id: string;
rid: string; rid: string;
msg: string; msg?: string;
ts: string | Date; // wm date issue ts: string | Date; // wm date issue
u: IUserMessage; u: IUserMessage;
_updatedAt: string | Date; _updatedAt: string | Date;
urls: IUrlFromServer[]; urls?: IUrlFromServer[];
mentions: IUserMention[]; mentions?: IUserMention[];
channels: IUserChannel[]; channels?: IUserChannel[];
md: MarkdownAST; md?: MarkdownAST;
file: IMessageFile; file?: IMessageFile;
files: IMessageFile[]; files?: IMessageFile[];
groupable: false; groupable?: boolean;
attachments: IMessageAttachment[]; attachments?: IAttachment[];
} }
export interface ILoadMoreMessage { export interface ILoadMoreMessage {
@ -135,7 +118,7 @@ export interface IMessage extends IMessageFromServer {
translations?: ITranslations[]; translations?: ITranslations[];
tmsg?: string; tmsg?: string;
blocks?: any; blocks?: any;
e2e?: string; e2e?: E2EType;
tshow?: boolean; tshow?: boolean;
subscription?: { id: string }; subscription?: { id: string };
} }

View File

@ -4,7 +4,7 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment'; import { IAttachment } from './IAttachment';
import { IMessage } from './IMessage'; import { IMessage } from './IMessage';
import { IServedBy } from './IServedBy'; import { IServedBy } from './IServedBy';
import { SubscriptionType } from './ISubscription'; import { IVisitor, SubscriptionType } from './ISubscription';
import { IUser } from './IUser'; import { IUser } from './IUser';
interface IRequestTranscript { interface IRequestTranscript {
@ -28,11 +28,8 @@ export interface IRoom {
broadcast: boolean; broadcast: boolean;
encrypted: boolean; encrypted: boolean;
ro: boolean; ro: boolean;
v?: { v?: IVisitor;
_id?: string; status?: string;
token?: string;
status: 'online' | 'busy' | 'away' | 'offline';
};
servedBy?: IServedBy; servedBy?: IServedBy;
departmentId?: string; departmentId?: string;
livechatData?: any; livechatData?: any;
@ -55,13 +52,11 @@ export enum OmnichannelSourceType {
API = 'api', API = 'api',
OTHER = 'other' // catch-all source type OTHER = 'other' // catch-all source type
} }
export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | 'broadcast' | ''> { export interface IOmnichannelRoom extends Partial<Omit<IRoom, 'default' | 'featured' | 'broadcast'>> {
_id: string;
rid: string;
t: SubscriptionType.OMNICHANNEL; t: SubscriptionType.OMNICHANNEL;
v: { v: IVisitor;
_id?: string;
token?: string;
status: 'online' | 'busy' | 'away' | 'offline';
};
email?: { email?: {
// Data used when the room is created from an email, via email Integration. // Data used when the room is created from an email, via email Integration.
inbox: string; inbox: string;
@ -83,6 +78,8 @@ export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | '
sidebarIcon?: string; sidebarIcon?: string;
// The default sidebar icon // The default sidebar icon
defaultIcon?: string; defaultIcon?: string;
_updatedAt?: Date;
queuedAt?: Date;
}; };
transcriptRequest?: IRequestTranscript; transcriptRequest?: IRequestTranscript;
servedBy?: IServedBy; servedBy?: IServedBy;
@ -91,18 +88,22 @@ export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | '
lastMessage?: IMessage & { token?: string }; lastMessage?: IMessage & { token?: string };
tags: any; tags?: string[];
closedAt: any; closedAt?: Date;
metrics: any; metrics?: any;
waitingResponse: any; waitingResponse?: any;
responseBy: any; responseBy?: any;
priorityId: any; priorityId?: any;
livechatData: any; livechatData?: any;
queuedAt?: Date; queuedAt?: Date;
ts: Date; ts: Date;
label?: string; label?: string;
crmData?: unknown; crmData?: unknown;
message?: string;
queueOrder?: string;
estimatedWaitingTimeQueue?: string;
estimatedServiceTimeAt?: Date;
} }
export type TRoomModel = IRoom & Model; export type TRoomModel = IRoom & Model;

View File

@ -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;
}

View File

@ -0,0 +1,11 @@
import { IServerRoomItem } from './IRoom';
import { IUser } from './IUser';
export type TSpotlightUser = Pick<IUser, '_id' | 'status' | 'name' | 'username'> & { outside: boolean };
export type ISpotlightRoom = Pick<IServerRoomItem, '_id' | 'name' | 't'> & Partial<Pick<IServerRoomItem, 'lastMessage'>>;
export interface ISpotlight {
users: TSpotlightUser[];
rooms: ISpotlightRoom[];
}

View File

@ -17,11 +17,11 @@ export enum SubscriptionType {
} }
export interface IVisitor { export interface IVisitor {
_id: string; _id?: string;
username: string; token?: string;
token: string; status: 'online' | 'busy' | 'away' | 'offline';
status: string; username?: string;
lastMessageTs: Date; lastMessageTs?: Date;
} }
export enum ERoomTypes { export enum ERoomTypes {
@ -76,7 +76,7 @@ export interface ISubscription {
jitsiTimeout?: number; jitsiTimeout?: number;
autoTranslate?: boolean; autoTranslate?: boolean;
autoTranslateLanguage?: string; autoTranslateLanguage?: string;
lastMessage?: ILastMessage; // TODO: we need to use IMessage here lastMessage?: ILastMessage | null; // TODO: we need to use IMessage here
hideUnreadStatus?: boolean; hideUnreadStatus?: boolean;
sysMes?: string[] | boolean; sysMes?: string[] | boolean;
uids?: string[]; uids?: string[];

View File

@ -1,5 +0,0 @@
export interface ITagsOmnichannel {
_id: string;
name: string;
departments: string[];
}

View File

@ -29,7 +29,7 @@ export interface IThreadResult {
channels?: IUserChannel[]; channels?: IUserChannel[];
replies?: string[]; replies?: string[];
tcount?: number; tcount?: number;
status?: string; status?: number;
tlm?: string | Date; tlm?: string | Date;
} }

View File

@ -132,7 +132,7 @@ export interface IUser extends IRocketChatRecord, Omit<ILoggedUser, 'username' |
name?: string; name?: string;
services?: IUserServices; services?: IUserServices;
emails?: IUserEmail[]; emails?: IUserEmail[];
status?: UserStatus; status: UserStatus;
statusConnection?: string; statusConnection?: string;
lastLogin?: Date; lastLogin?: Date;
avatarOrigin?: string; avatarOrigin?: string;

View File

@ -25,6 +25,7 @@ export * from './IRocketChat';
export * from './ICertificate'; export * from './ICertificate';
export * from './IUrl'; export * from './IUrl';
export * from './ICredentials'; export * from './ICredentials';
export * from './ISearch';
export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> { export interface IBaseScreen<T extends Record<string, object | undefined>, S extends string> {
navigation: StackNavigationProp<T, S>; navigation: StackNavigationProp<T, S>;

View File

@ -1,4 +1,5 @@
// ACTIONS // ACTIONS
import { TActionInquiry } from '../../ee/omnichannel/actions/inquiry';
import { TActionActiveUsers } from '../../actions/activeUsers'; import { TActionActiveUsers } from '../../actions/activeUsers';
import { TActionApp } from '../../actions/app'; import { TActionApp } from '../../actions/app';
import { TActionCreateChannel } from '../../actions/createChannel'; import { TActionCreateChannel } from '../../actions/createChannel';
@ -30,6 +31,7 @@ import { ISelectedUsers } from '../../reducers/selectedUsers';
import { IServer } from '../../reducers/server'; import { IServer } from '../../reducers/server';
import { TSettingsState } from '../../reducers/settings'; import { TSettingsState } from '../../reducers/settings';
import { IShare } from '../../reducers/share'; import { IShare } from '../../reducers/share';
import { IInquiry } from '../../ee/omnichannel/reducers/inquiry';
import { IPermissionsState } from '../../reducers/permissions'; import { IPermissionsState } from '../../reducers/permissions';
import { IEnterpriseModules } from '../../reducers/enterpriseModules'; import { IEnterpriseModules } from '../../reducers/enterpriseModules';
@ -50,7 +52,7 @@ export interface IApplicationState {
usersTyping: any; usersTyping: any;
inviteLinks: IInviteLinks; inviteLinks: IInviteLinks;
createDiscussion: ICreateDiscussion; createDiscussion: ICreateDiscussion;
inquiry: any; inquiry: IInquiry;
enterpriseModules: IEnterpriseModules; enterpriseModules: IEnterpriseModules;
encryption: IEncryption; encryption: IEncryption;
permissions: IPermissionsState; permissions: IPermissionsState;
@ -71,5 +73,6 @@ export type TApplicationActions = TActionActiveUsers &
TActionsShare & TActionsShare &
TActionServer & TActionServer &
TActionApp & TActionApp &
TActionInquiry &
TActionPermissions & TActionPermissions &
TActionEnterpriseModules; TActionEnterpriseModules;

View File

@ -52,4 +52,51 @@ export type ChannelsEndpoints = {
'channels.convertToTeam': { 'channels.convertToTeam': {
POST: (params: { channelId: string; channelName: string }) => { team: ITeam }; POST: (params: { channelId: string; channelName: string }) => { team: ITeam };
}; };
'channels.info': {
GET: (params: { roomId: string }) => { channel: IServerRoomItem };
};
'channels.counters': {
GET: (params: { roomId: string }) => {
joined: boolean;
members: number;
unreads: number;
unreadsFrom: Date;
msgs: number;
latest: Date;
userMentions: number;
};
};
'channels.join': {
POST: (params: { roomId: string; joinCode: string | null }) => { channel: IServerRoomItem };
};
'channels.close': {
POST: (params: { roomId: string }) => {};
};
'channels.kick': {
POST: (params: { roomId: string; userId: string }) => {};
};
'channels.delete': {
POST: (params: { roomId: string }) => {};
};
'channels.leave': {
POST: (params: { roomId: string }) => {};
};
'channels.addModerator': {
POST: (params: { roomId: string; userId: string }) => {};
};
'channels.removeModerator': {
POST: (params: { roomId: string; userId: string }) => {};
};
'channels.addOwner': {
POST: (params: { roomId: string; userId: string }) => {};
};
'channels.removeOwner': {
POST: (params: { roomId: string; userId: string }) => {};
};
'channels.addLeader': {
POST: (params: { roomId: string; userId: string }) => {};
};
'channels.removeLeader': {
POST: (params: { roomId: string; userId: string }) => {};
};
}; };

View File

@ -11,9 +11,24 @@ export type ChatEndpoints = {
'chat.followMessage': { 'chat.followMessage': {
POST: (params: { mid: IMessage['_id'] }) => void; POST: (params: { mid: IMessage['_id'] }) => void;
}; };
'chat.unStarMessage': {
POST: (params: { messageId: IMessage['_id'] }) => void;
};
'chat.starMessage': {
POST: (params: { messageId: IMessage['_id'] }) => void;
};
'chat.unfollowMessage': { 'chat.unfollowMessage': {
POST: (params: { mid: IMessage['_id'] }) => void; POST: (params: { mid: IMessage['_id'] }) => void;
}; };
'chat.unPinMessage': {
POST: (params: { messageId: IMessage['_id'] }) => void;
};
'chat.pinMessage': {
POST: (params: { messageId: IMessage['_id'] }) => void;
};
'chat.reportMessage': {
POST: (params: { messageId: IMessage['_id']; description: string }) => void;
};
'chat.getDiscussions': { 'chat.getDiscussions': {
GET: (params: { roomId: IRoom['_id']; text?: string; offset: number; count: number }) => { GET: (params: { roomId: IRoom['_id']; text?: string; offset: number; count: number }) => {
messages: IMessage[]; messages: IMessage[];
@ -39,4 +54,10 @@ export type ChatEndpoints = {
message: Pick<IMessage, '_id' | 'rid' | 'u'>; message: Pick<IMessage, '_id' | 'rid' | 'u'>;
}; };
}; };
'chat.react': {
POST: (params: { emoji: string; messageId: string }) => void;
};
'chat.ignoreUser': {
GET: (params: { rid: string; userId: string; ignore: boolean }) => {};
};
}; };

View File

@ -46,4 +46,27 @@ export type GroupsEndpoints = {
'groups.convertToTeam': { 'groups.convertToTeam': {
POST: (params: { roomId: string; roomName: string }) => { team: ITeam }; POST: (params: { roomId: string; roomName: string }) => { team: ITeam };
}; };
'groups.counters': {
GET: (params: { roomId: string }) => {
joined: boolean;
members: number;
unreads: number;
unreadsFrom: Date;
msgs: number;
latest: Date;
userMentions: number;
};
};
'groups.close': {
POST: (params: { roomId: string }) => {};
};
'groups.kick': {
POST: (params: { roomId: string; userId: string }) => {};
};
'groups.delete': {
POST: (params: { roomId: string }) => {};
};
'groups.leave': {
POST: (params: { roomId: string }) => {};
};
}; };

View File

@ -38,4 +38,16 @@ export type ImEndpoints = {
messages: IMessageFromServer[]; messages: IMessageFromServer[];
}; };
}; };
'im.close': {
POST: (params: { roomId: string }) => {};
};
'im.kick': {
POST: (params: { roomId: string; userId: string }) => {};
};
'im.delete': {
POST: (params: { roomId: string }) => {};
};
'im.leave': {
POST: (params: { roomId: string }) => {};
};
}; };

View File

@ -15,6 +15,7 @@ import { UsersEndpoints } from './users';
import { TeamsEndpoints } from './teams'; import { TeamsEndpoints } from './teams';
import { E2eEndpoints } from './e2e'; import { E2eEndpoints } from './e2e';
import { SubscriptionsEndpoints } from './subscriptions'; import { SubscriptionsEndpoints } from './subscriptions';
import { VideoConferenceEndpoints } from './videoConference';
export type Endpoints = ChannelsEndpoints & export type Endpoints = ChannelsEndpoints &
ChatEndpoints & ChatEndpoints &
@ -32,4 +33,5 @@ export type Endpoints = ChannelsEndpoints &
UsersEndpoints & UsersEndpoints &
TeamsEndpoints & TeamsEndpoints &
E2eEndpoints & E2eEndpoints &
SubscriptionsEndpoints; SubscriptionsEndpoints &
VideoConferenceEndpoints;

View File

@ -1,3 +1,4 @@
import { ICannedResponse } from '../../ICannedResponse';
import { ILivechatAgent } from '../../ILivechatAgent'; import { ILivechatAgent } from '../../ILivechatAgent';
import { ILivechatDepartment } from '../../ILivechatDepartment'; import { ILivechatDepartment } from '../../ILivechatDepartment';
import { ILivechatDepartmentAgents } from '../../ILivechatDepartmentAgents'; import { ILivechatDepartmentAgents } from '../../ILivechatDepartmentAgents';
@ -102,6 +103,8 @@ export type OmnichannelEndpoints = {
{ {
_id: string; _id: string;
label: string; label: string;
visibility?: string;
scope?: string;
} }
]; ];
}>; }>;
@ -190,4 +193,10 @@ export type OmnichannelEndpoints = {
total: number; total: number;
}; };
}; };
'canned-responses': {
GET: (params: PaginatedRequest<{ scope?: string; departmentId?: string; text?: string }>) => PaginatedResult<{
cannedResponses: ICannedResponse[];
}>;
};
}; };

View File

@ -34,4 +34,7 @@ export type UsersEndpoints = {
'users.register': { 'users.register': {
POST: (params: { name: string; email: string; username: string; pass: string }) => { user: IUserRegistered }; POST: (params: { name: string; email: string; username: string; pass: string }) => { user: IUserRegistered };
}; };
'users.setStatus': {
POST: (params: { status: string; message: string }) => {};
};
}; };

View File

@ -0,0 +1,5 @@
export type VideoConferenceEndpoints = {
'video-conference/jitsi.update-timeout': {
POST: (params: { roomId: string }) => void;
};
};

View File

@ -1,55 +0,0 @@
import * as types from '../../../actions/actionsTypes';
export function inquirySetEnabled(enabled) {
return {
type: types.INQUIRY.SET_ENABLED,
enabled
};
}
export function inquiryReset() {
return {
type: types.INQUIRY.RESET
};
}
export function inquiryQueueAdd(inquiry) {
return {
type: types.INQUIRY.QUEUE_ADD,
inquiry
};
}
export function inquiryQueueUpdate(inquiry) {
return {
type: types.INQUIRY.QUEUE_UPDATE,
inquiry
};
}
export function inquiryQueueRemove(inquiryId) {
return {
type: types.INQUIRY.QUEUE_REMOVE,
inquiryId
};
}
export function inquiryRequest() {
return {
type: types.INQUIRY.REQUEST
};
}
export function inquirySuccess(inquiries) {
return {
type: types.INQUIRY.SUCCESS,
inquiries
};
}
export function inquiryFailure(error) {
return {
type: types.INQUIRY.FAILURE,
error
};
}

View File

@ -0,0 +1,84 @@
import { Action } from 'redux';
import { IOmnichannelRoom } from '../../../definitions';
import { INQUIRY } from '../../../actions/actionsTypes';
interface IInquirySetEnabled extends Action {
enabled: boolean;
}
interface IInquiryQueueAddAndUpdate extends Action {
inquiry: IOmnichannelRoom;
}
interface IInquirySuccess extends Action {
inquiries: IOmnichannelRoom[];
}
interface IInquiryQueueRemove extends Action {
inquiryId: string;
}
interface IInquiryFailure extends Action {
error: unknown;
}
export type TActionInquiry = IInquirySetEnabled &
IInquiryQueueAddAndUpdate &
IInquirySuccess &
IInquiryQueueRemove &
IInquiryFailure;
export function inquirySetEnabled(enabled: boolean): IInquirySetEnabled {
return {
type: INQUIRY.SET_ENABLED,
enabled
};
}
export function inquiryReset(): Action {
return {
type: INQUIRY.RESET
};
}
export function inquiryQueueAdd(inquiry: IOmnichannelRoom): IInquiryQueueAddAndUpdate {
return {
type: INQUIRY.QUEUE_ADD,
inquiry
};
}
export function inquiryQueueUpdate(inquiry: IOmnichannelRoom): IInquiryQueueAddAndUpdate {
return {
type: INQUIRY.QUEUE_UPDATE,
inquiry
};
}
export function inquiryQueueRemove(inquiryId: string): IInquiryQueueRemove {
return {
type: INQUIRY.QUEUE_REMOVE,
inquiryId
};
}
export function inquiryRequest(): Action {
return {
type: INQUIRY.REQUEST
};
}
export function inquirySuccess(inquiries: IOmnichannelRoom[]): IInquirySuccess {
return {
type: INQUIRY.SUCCESS,
inquiries
};
}
export function inquiryFailure(error: unknown): IInquiryFailure {
return {
type: INQUIRY.FAILURE,
error
};
}

View File

@ -1,19 +1,28 @@
import React, { memo, useEffect, useState } from 'react'; import React, { memo, useEffect, useState } from 'react';
import { Switch, View } from 'react-native'; import { Switch, View } from 'react-native';
import PropTypes from 'prop-types';
import * as List from '../../../containers/List'; import * as List from '../../../containers/List';
import styles from '../../../views/RoomsListView/styles'; import styles from '../../../views/RoomsListView/styles';
import { SWITCH_TRACK_COLOR, themes } from '../../../constants/colors'; import { SWITCH_TRACK_COLOR, themes } from '../../../constants/colors';
import { withTheme } from '../../../theme'; import { useTheme } from '../../../theme';
import UnreadBadge from '../../../presentation/UnreadBadge'; import UnreadBadge from '../../../presentation/UnreadBadge';
import RocketChat from '../../../lib/rocketchat'; import RocketChat from '../../../lib/rocketchat';
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib'; import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib';
import { IUser } from '../../../definitions/IUser';
const OmnichannelStatus = memo(({ searching, goQueue, theme, queueSize, inquiryEnabled, user }) => { interface IOmnichannelStatus {
if (searching > 0 || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) { searching: boolean;
goQueue: () => void;
queueSize: number;
inquiryEnabled: boolean;
user: IUser;
}
const OmnichannelStatus = memo(({ searching, goQueue, queueSize, inquiryEnabled, user }: IOmnichannelStatus) => {
if (searching || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) {
return null; return null;
} }
const { theme } = useTheme();
const [status, setStatus] = useState(isOmnichannelStatusAvailable(user)); const [status, setStatus] = useState(isOmnichannelStatusAvailable(user));
useEffect(() => { useEffect(() => {
@ -48,16 +57,4 @@ const OmnichannelStatus = memo(({ searching, goQueue, theme, queueSize, inquiryE
); );
}); });
OmnichannelStatus.propTypes = { export default OmnichannelStatus;
searching: PropTypes.bool,
goQueue: PropTypes.func,
queueSize: PropTypes.number,
inquiryEnabled: PropTypes.bool,
theme: PropTypes.string,
user: PropTypes.shape({
roles: PropTypes.array,
statusLivechat: PropTypes.string
})
};
export default withTheme(OmnichannelStatus);

View File

@ -1,21 +1,24 @@
import RocketChat from '../../../lib/rocketchat'; import sdk from '../../../lib/rocketchat/services/sdk';
import { IUser } from '../../../definitions';
import EventEmitter from '../../../utils/events'; import EventEmitter from '../../../utils/events';
import subscribeInquiry from './subscriptions/inquiry'; import subscribeInquiry from './subscriptions/inquiry';
export const isOmnichannelStatusAvailable = user => user?.statusLivechat === 'available'; export const isOmnichannelStatusAvailable = (user: IUser): boolean => user?.statusLivechat === 'available';
// RC 0.26.0 // RC 0.26.0
export const changeLivechatStatus = () => RocketChat.methodCallWrapper('livechat:changeLivechatStatus'); export const changeLivechatStatus = () => sdk.methodCallWrapper('livechat:changeLivechatStatus');
// RC 2.4.0 // RC 2.4.0
export const getInquiriesQueued = () => RocketChat.sdk.get('livechat/inquiries.queued'); // @ts-ignore
export const getInquiriesQueued = () => sdk.get('livechat/inquiries.queued');
// this inquiry is added to the db by the subscriptions stream // this inquiry is added to the db by the subscriptions stream
// and will be removed by the queue stream // and will be removed by the queue stream
// RC 2.4.0 // RC 2.4.0
export const takeInquiry = inquiryId => RocketChat.methodCallWrapper('livechat:takeInquiry', inquiryId); export const takeInquiry = (inquiryId: string) => sdk.methodCallWrapper('livechat:takeInquiry', inquiryId);
class Omnichannel { class Omnichannel {
private inquirySub: { stop: () => void } | null;
constructor() { constructor() {
this.inquirySub = null; this.inquirySub = null;
EventEmitter.addEventListener('INQUIRY_SUBSCRIBE', this.subscribeInquiry); EventEmitter.addEventListener('INQUIRY_SUBSCRIBE', this.subscribeInquiry);
@ -36,5 +39,5 @@ class Omnichannel {
}; };
} }
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const omnichannel = new Omnichannel(); const omnichannel = new Omnichannel();

View File

@ -2,11 +2,28 @@ import log from '../../../../utils/log';
import { store } from '../../../../lib/auxStore'; import { store } from '../../../../lib/auxStore';
import RocketChat from '../../../../lib/rocketchat'; import RocketChat from '../../../../lib/rocketchat';
import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate, inquiryRequest } from '../../actions/inquiry'; import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate, inquiryRequest } from '../../actions/inquiry';
import sdk from '../../../../lib/rocketchat/services/sdk';
import { ILivechatDepartment } from '../../../../definitions/ILivechatDepartment';
import { IOmnichannelRoom } from '../../../../definitions';
const removeListener = listener => listener.stop(); interface IArgsQueueOmnichannel extends IOmnichannelRoom {
type: string;
}
let connectedListener; interface IDdpMessage {
let queueListener; msg: string;
collection: string;
id: string;
fields: {
eventName: string;
args: IArgsQueueOmnichannel[];
};
}
const removeListener = (listener: any) => listener.stop();
let connectedListener: any;
let queueListener: any;
const streamTopic = 'stream-livechat-inquiry-queue-observer'; const streamTopic = 'stream-livechat-inquiry-queue-observer';
@ -15,7 +32,7 @@ export default function subscribeInquiry() {
store.dispatch(inquiryRequest()); store.dispatch(inquiryRequest());
}; };
const handleQueueMessageReceived = ddpMessage => { const handleQueueMessageReceived = (ddpMessage: IDdpMessage) => {
const [{ type, ...sub }] = ddpMessage.fields.args; const [{ type, ...sub }] = ddpMessage.fields.args;
// added can be ignored, since it is handled by 'changed' event // added can be ignored, since it is handled by 'changed' event
@ -26,7 +43,9 @@ export default function subscribeInquiry() {
// if the sub isn't on the queue anymore // if the sub isn't on the queue anymore
if (sub.status !== 'queued') { if (sub.status !== 'queued') {
// remove it from the queue // remove it from the queue
store.dispatch(inquiryQueueRemove(sub._id)); if (sub._id) {
store.dispatch(inquiryQueueRemove(sub._id));
}
return; return;
} }
@ -53,23 +72,28 @@ export default function subscribeInquiry() {
} }
}; };
connectedListener = RocketChat.onStreamData('connected', handleConnection); connectedListener = sdk.onStreamData('connected', handleConnection);
queueListener = RocketChat.onStreamData(streamTopic, handleQueueMessageReceived); queueListener = sdk.onStreamData(streamTopic, handleQueueMessageReceived);
try { try {
const { user } = store.getState().login; const { user } = store.getState().login;
RocketChat.getAgentDepartments(user.id).then(result => {
if (!user.id) {
throw new Error('inquiry: @subscribeInquiry user.id not found');
}
RocketChat.getAgentDepartments(user.id).then((result: { success: boolean; departments: ILivechatDepartment[] }) => {
if (result.success) { if (result.success) {
const { departments } = result; const { departments } = result;
if (!departments.length || RocketChat.hasRole('livechat-manager')) { if (!departments.length || RocketChat.hasRole('livechat-manager')) {
RocketChat.subscribe(streamTopic, 'public').catch(e => console.log(e)); sdk.subscribe(streamTopic, 'public').catch((e: unknown) => console.log(e));
} }
const departmentIds = departments.map(({ departmentId }) => departmentId); const departmentIds = departments.map(({ departmentId }) => departmentId);
departmentIds.forEach(departmentId => { departmentIds.forEach(departmentId => {
// subscribe to all departments of the agent // subscribe to all departments of the agent
RocketChat.subscribe(streamTopic, `department/${departmentId}`).catch(e => console.log(e)); sdk.subscribe(streamTopic, `department/${departmentId}`).catch((e: unknown) => console.log(e));
}); });
} }
}); });

View File

@ -0,0 +1,98 @@
import {
inquiryFailure,
inquiryQueueAdd,
inquiryQueueRemove,
inquiryQueueUpdate,
inquiryReset,
inquirySetEnabled,
inquirySuccess
} from '../actions/inquiry';
import { mockedStore } from '../../../reducers/mockedStore';
import { initialState } from './inquiry';
import { IOmnichannelRoom, OmnichannelSourceType, SubscriptionType } from '../../../definitions';
describe('test inquiry reduce', () => {
const enabledObj = {
enabled: true
};
const queued: IOmnichannelRoom = {
_id: '_id',
rid: 'rid',
name: 'Rocket Chat',
ts: new Date(),
message: 'ola',
status: 'queued',
v: {
_id: 'id-visitor',
username: 'guest-24',
token: '123456789',
status: 'online'
},
t: SubscriptionType.OMNICHANNEL,
queueOrder: '1',
estimatedWaitingTimeQueue: '0',
estimatedServiceTimeAt: new Date(),
source: {
type: OmnichannelSourceType.WIDGET,
_updatedAt: new Date(),
queuedAt: new Date()
}
};
const error = 'Error Test';
it('should return inital state', () => {
const state = mockedStore.getState().inquiry;
expect(state).toEqual(initialState);
});
it('should return correct inquiry state after dispatch inquirySetEnabled action', () => {
mockedStore.dispatch(inquirySetEnabled(enabledObj.enabled));
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual({ ...initialState, ...enabledObj });
});
it('after inquiry state is modified, should return inquiry state as initial state after dispatch inquiryReset action', () => {
mockedStore.dispatch(inquiryReset());
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual(initialState);
});
it('should return correct inquiry state after dispatch inquiryQueueAdd action', () => {
mockedStore.dispatch(inquiryQueueAdd(queued));
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual({ ...initialState, queued: [queued] });
});
it('should update correct inquiry state after dispatch inquiryQueueUpdate action', () => {
const modifiedQueued: IOmnichannelRoom = { ...queued, message: 'inquiryQueueUpdate' };
mockedStore.dispatch(inquiryQueueUpdate(modifiedQueued));
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual({ ...initialState, queued: [modifiedQueued] });
});
it('should remove correct from queue in inquiry state after dispatch inquiryQueueRemove action', () => {
mockedStore.dispatch(inquiryQueueRemove(queued._id));
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual(initialState);
});
it('should return correct inquiry state after dispatch inquirySuccess action', () => {
mockedStore.dispatch(inquirySuccess([queued]));
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual({ ...initialState, queued: [queued] });
});
it('after inquiry state is modified, should return inquiry state as initial state after dispatch inquiryReset action', () => {
mockedStore.dispatch(inquiryReset());
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual(initialState);
});
it('should return correct inquiry state after dispatch inquiryFailure action', () => {
mockedStore.dispatch(inquiryFailure(error));
const { inquiry } = mockedStore.getState();
expect(inquiry).toEqual({ ...initialState, error });
});
});

View File

@ -1,12 +1,19 @@
import { IOmnichannelRoom, TApplicationActions } from '../../../definitions';
import { INQUIRY } from '../../../actions/actionsTypes'; import { INQUIRY } from '../../../actions/actionsTypes';
const initialState = { export interface IInquiry {
enabled: boolean;
queued: IOmnichannelRoom[];
error: any;
}
export const initialState: IInquiry = {
enabled: false, enabled: false,
queued: [], queued: [],
error: {} error: {}
}; };
export default function inquiry(state = initialState, action) { export default function inquiry(state = initialState, action: TApplicationActions): IInquiry {
switch (action.type) { switch (action.type) {
case INQUIRY.SUCCESS: case INQUIRY.SUCCESS:
return { return {

View File

@ -1,5 +1,7 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const getInquiryQueue = state => state.inquiry.queued; import { IApplicationState } from '../../../definitions';
const getInquiryQueue = (state: IApplicationState) => state.inquiry.queued;
export const getInquiryQueueSelector = createSelector([getInquiryQueue], queue => queue); export const getInquiryQueueSelector = createSelector([getInquiryQueue], queue => queue);

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { CompositeNavigationProp } from '@react-navigation/native';
import { FlatList } from 'react-native'; import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { FlatList, ListRenderItem } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
@ -19,18 +20,50 @@ import * as HeaderButton from '../../../containers/HeaderButton';
import RocketChat from '../../../lib/rocketchat'; import RocketChat from '../../../lib/rocketchat';
import { events, logEvent } from '../../../utils/log'; import { events, logEvent } from '../../../utils/log';
import { getInquiryQueueSelector } from '../selectors/inquiry'; import { getInquiryQueueSelector } from '../selectors/inquiry';
import { IOmnichannelRoom, IApplicationState } from '../../../definitions';
import { DisplayMode } from '../../../constants/constantDisplayMode';
import { ChatsStackParamList } from '../../../stacks/types';
import { MasterDetailInsideStackParamList } from '../../../stacks/MasterDetailStack/types';
import { TSettingsValues } from '../../../reducers/settings';
interface INavigationOptions {
isMasterDetail: boolean;
navigation: CompositeNavigationProp<
StackNavigationProp<ChatsStackParamList, 'QueueListView'>,
StackNavigationProp<MasterDetailInsideStackParamList>
>;
}
interface IQueueListView extends INavigationOptions {
user: {
id: string;
username: string;
token: string;
};
width: number;
queued: IOmnichannelRoom[];
server: string;
useRealName?: TSettingsValues;
theme: string;
showAvatar: any;
displayMode: DisplayMode;
}
const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12;
const getItemLayout = (data, index) => ({ const getItemLayout = (data: IOmnichannelRoom[] | null | undefined, index: number) => ({
length: ROW_HEIGHT, length: ROW_HEIGHT,
offset: ROW_HEIGHT * index, offset: ROW_HEIGHT * index,
index index
}); });
const keyExtractor = item => item.rid; const keyExtractor = (item: IOmnichannelRoom) => item.rid;
class QueueListView extends React.Component { class QueueListView extends React.Component<IQueueListView, any> {
static navigationOptions = ({ navigation, isMasterDetail }) => { private getScrollRef?: React.Ref<FlatList<IOmnichannelRoom>>;
const options = {
private onEndReached: ((info: { distanceFromEnd: number }) => void) | null | undefined;
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => {
const options: StackNavigationOptions = {
title: I18n.t('Queued_chats') title: I18n.t('Queued_chats')
}; };
if (isMasterDetail) { if (isMasterDetail) {
@ -39,24 +72,7 @@ class QueueListView extends React.Component {
return options; return options;
}; };
static propTypes = { shouldComponentUpdate(nextProps: IQueueListView) {
user: PropTypes.shape({
id: PropTypes.string,
username: PropTypes.string,
token: PropTypes.string
}),
isMasterDetail: PropTypes.bool,
width: PropTypes.number,
queued: PropTypes.array,
server: PropTypes.string,
useRealName: PropTypes.bool,
navigation: PropTypes.object,
theme: PropTypes.string,
showAvatar: PropTypes.bool,
displayMode: PropTypes.string
};
shouldComponentUpdate(nextProps) {
const { queued } = this.props; const { queued } = this.props;
if (!dequal(nextProps.queued, queued)) { if (!dequal(nextProps.queued, queued)) {
return true; return true;
@ -65,7 +81,7 @@ class QueueListView extends React.Component {
return false; return false;
} }
onPressItem = (item = {}) => { onPressItem = (item = {} as IOmnichannelRoom) => {
logEvent(events.QL_GO_ROOM); logEvent(events.QL_GO_ROOM);
const { navigation, isMasterDetail } = this.props; const { navigation, isMasterDetail } = this.props;
if (isMasterDetail) { if (isMasterDetail) {
@ -84,13 +100,13 @@ class QueueListView extends React.Component {
}); });
}; };
getRoomTitle = item => RocketChat.getRoomTitle(item); getRoomTitle = (item: IOmnichannelRoom) => RocketChat.getRoomTitle(item);
getRoomAvatar = item => RocketChat.getRoomAvatar(item); getRoomAvatar = (item: IOmnichannelRoom) => RocketChat.getRoomAvatar(item);
getUidDirectMessage = room => RocketChat.getUidDirectMessage(room); getUidDirectMessage = (room: IOmnichannelRoom) => RocketChat.getUidDirectMessage(room);
renderItem = ({ item }) => { renderItem: ListRenderItem<IOmnichannelRoom> = ({ item }) => {
const { const {
user: { id: userId, username, token }, user: { id: userId, username, token },
server, server,
@ -152,7 +168,7 @@ class QueueListView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state), user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
server: state.server.server, server: state.server.server,

View File

@ -4,7 +4,10 @@ import { MESSAGES_TABLE } from '../model/Message';
const getCollection = (db: TAppDatabase) => db.get(MESSAGES_TABLE); const getCollection = (db: TAppDatabase) => db.get(MESSAGES_TABLE);
export const getMessageById = async (messageId: string) => { export const getMessageById = async (messageId: string | null) => {
if (!messageId) {
return null;
}
const db = database.active; const db = database.active;
const messageCollection = getCollection(db); const messageCollection = getCollection(db);
try { try {

View File

@ -2,7 +2,7 @@ import { ILastMessage, IMessage, IThreadResult } from '../../../definitions';
import messagesStatus from '../../../constants/messagesStatus'; import messagesStatus from '../../../constants/messagesStatus';
import normalizeMessage from './normalizeMessage'; import normalizeMessage from './normalizeMessage';
export default (message: Partial<IMessage> | IThreadResult | ILastMessage): IMessage | IThreadResult => { export default (message: Partial<IMessage> | IThreadResult | ILastMessage): IMessage | IThreadResult | null => {
message.status = messagesStatus.SENT; message.status = messagesStatus.SENT;
return normalizeMessage(message); return normalizeMessage(message);
}; };

View File

@ -1,8 +1,11 @@
import moment from 'moment'; import moment from 'moment';
import parseUrls from './parseUrls'; import parseUrls from './parseUrls';
import type { IAttachment, IMessage, IThreadResult } from '../../../definitions';
function normalizeAttachments(msg) { type TMsg = IMessage & IAttachment;
function normalizeAttachments(msg: TMsg) {
if (typeof msg.attachments !== typeof [] || !msg.attachments || !msg.attachments.length) { if (typeof msg.attachments !== typeof [] || !msg.attachments || !msg.attachments.length) {
msg.attachments = []; msg.attachments = [];
} }
@ -11,17 +14,17 @@ function normalizeAttachments(msg) {
if (att.ts) { if (att.ts) {
att.ts = moment(att.ts).toDate(); att.ts = moment(att.ts).toDate();
} }
att = normalizeAttachments(att); att = normalizeAttachments(att as TMsg);
return att; return att;
}); });
return msg; return msg;
} }
export default msg => { export default (msg: any): IMessage | IThreadResult | null => {
if (!msg) { if (!msg) {
return null; return null;
} }
msg = normalizeAttachments(msg); msg = normalizeAttachments(msg as TMsg);
msg.reactions = msg.reactions || []; msg.reactions = msg.reactions || [];
msg.unread = msg.unread || false; msg.unread = msg.unread || false;
// TODO: api problems // TODO: api problems
@ -30,18 +33,19 @@ export default msg => {
// } else { // } else {
// msg.reactions = Object.keys(msg.reactions).map(key => ({ emoji: key, usernames: msg.reactions[key].usernames.map(username => ({ value: username })) })); // msg.reactions = Object.keys(msg.reactions).map(key => ({ emoji: key, usernames: msg.reactions[key].usernames.map(username => ({ value: username })) }));
// } // }
if (!Array.isArray(msg.reactions)) { if (!Array.isArray(msg.reactions)) {
msg.reactions = Object.keys(msg.reactions).map(key => ({ msg.reactions = Object.keys(msg.reactions).map(key => ({
_id: `${msg._id}${key}`, _id: `${msg._id}${key}`,
emoji: key, emoji: key,
usernames: msg.reactions[key].usernames usernames: msg.reactions ? msg.reactions[key].usernames : []
})); }));
} }
if (msg.translations && Object.keys(msg.translations).length) { if (msg.translations && Object.keys(msg.translations).length) {
msg.translations = Object.keys(msg.translations).map(key => ({ msg.translations = Object.keys(msg.translations).map(key => ({
_id: `${msg._id}${key}`, _id: `${msg._id}${key}`,
language: key, language: key,
value: msg.translations[key] value: msg.translations ? msg.translations[key] : ''
})); }));
msg.autoTranslate = true; msg.autoTranslate = true;
} }

View File

@ -7,7 +7,7 @@ import log from '../../utils/log';
import random from '../../utils/random'; import random from '../../utils/random';
import { Encryption } from '../encryption'; import { Encryption } from '../encryption';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../encryption/constants'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../encryption/constants';
import { IMessage, IUser, TMessageModel } from '../../definitions'; import { E2EType, IMessage, IUser, TMessageModel } from '../../definitions';
import sdk from '../rocketchat/services/sdk'; import sdk from '../rocketchat/services/sdk';
const changeMessageStatus = async (id: string, status: number, tmid?: string, message?: IMessage) => { const changeMessageStatus = async (id: string, status: number, tmid?: string, message?: IMessage) => {
@ -145,7 +145,7 @@ export default async function (
tm.u = tMessageRecord.u; tm.u = tMessageRecord.u;
tm.t = message.t; tm.t = message.t;
if (message.t === E2E_MESSAGE_TYPE) { if (message.t === E2E_MESSAGE_TYPE) {
tm.e2e = E2E_STATUS.DONE; tm.e2e = E2E_STATUS.DONE as E2EType;
} }
}) })
); );
@ -170,7 +170,7 @@ export default async function (
}; };
tm.t = message.t; tm.t = message.t;
if (message.t === E2E_MESSAGE_TYPE) { if (message.t === E2E_MESSAGE_TYPE) {
tm.e2e = E2E_STATUS.DONE; tm.e2e = E2E_STATUS.DONE as E2EType;
} }
}) })
); );
@ -203,7 +203,7 @@ export default async function (
} }
m.t = message.t; m.t = message.t;
if (message.t === E2E_MESSAGE_TYPE) { if (message.t === E2E_MESSAGE_TYPE) {
m.e2e = E2E_STATUS.DONE; m.e2e = E2E_STATUS.DONE as E2EType;
} }
}) })
); );

View File

@ -195,7 +195,10 @@ const createOrUpdateSubscription = async (subscription: ISubscription, room: IRo
if (tmp.lastMessage && !rooms.includes(tmp.rid)) { if (tmp.lastMessage && !rooms.includes(tmp.rid)) {
const lastMessage = buildMessage(tmp.lastMessage); const lastMessage = buildMessage(tmp.lastMessage);
const messagesCollection = db.get('messages'); const messagesCollection = db.get('messages');
const messageRecord = await getMessageById(lastMessage._id); let messageRecord = {} as TMessageModel | null;
if (lastMessage) {
messageRecord = await getMessageById(lastMessage._id);
}
if (messageRecord) { if (messageRecord) {
batch.push( batch.push(
@ -206,9 +209,11 @@ const createOrUpdateSubscription = async (subscription: ISubscription, room: IRo
} else { } else {
batch.push( batch.push(
messagesCollection.prepareCreate(m => { messagesCollection.prepareCreate(m => {
m._raw = sanitizedRaw({ id: lastMessage._id }, messagesCollection.schema); if (lastMessage) {
if (m.subscription) { m._raw = sanitizedRaw({ id: lastMessage._id }, messagesCollection.schema);
m.subscription.id = lastMessage.rid; if (m.subscription) {
m.subscription.id = lastMessage.rid;
}
} }
return Object.assign(m, lastMessage); return Object.assign(m, lastMessage);
}) })

View File

@ -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;
}
};

View File

@ -50,6 +50,7 @@ import getRoom from './methods/getRoom';
import isGroupChat from './methods/isGroupChat'; import isGroupChat from './methods/isGroupChat';
import roomTypeToApiType from './methods/roomTypeToApiType'; import roomTypeToApiType from './methods/roomTypeToApiType';
import getUserInfo from './services/getUserInfo'; import getUserInfo from './services/getUserInfo';
import * as search from './methods/search';
// Services // Services
import sdk from './services/sdk'; import sdk from './services/sdk';
import toggleFavorite from './services/toggleFavorite'; import toggleFavorite from './services/toggleFavorite';
@ -85,6 +86,7 @@ const RocketChat = {
CURRENT_SERVER, CURRENT_SERVER,
CERTIFICATE_KEY, CERTIFICATE_KEY,
...restAPis, ...restAPis,
...search,
callJitsi, callJitsi,
callJitsiWithoutServer, callJitsiWithoutServer,
async subscribeRooms() { async subscribeRooms() {
@ -252,93 +254,6 @@ const RocketChat = {
getRooms, getRooms,
readMessages, readMessages,
resendMessage, 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() { createGroupChat() {
const { users } = reduxStore.getState().selectedUsers; const { users } = reduxStore.getState().selectedUsers;
const usernames = users.map(u => u.name).join(','); const usernames = users.map(u => u.name).join(',');

View File

@ -2,6 +2,7 @@ import sdk from './sdk';
import { TEAM_TYPE } from '../../../definitions/ITeam'; import { TEAM_TYPE } from '../../../definitions/ITeam';
import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType';
import { SubscriptionType, INotificationPreferences } from '../../../definitions'; import { SubscriptionType, INotificationPreferences } from '../../../definitions';
import { ISpotlight } from '../../../definitions/ISpotlight';
export const createChannel = ({ export const createChannel = ({
name, name,
@ -53,14 +54,12 @@ export const e2eUpdateGroupKey = (uid: string, rid: string, key: string): any =>
// RC 0.70.0 // RC 0.70.0
sdk.post('e2e.updateGroupKey', { uid, rid, key }); sdk.post('e2e.updateGroupKey', { uid, rid, key });
export const e2eRequestRoomKey = (rid: string, e2eKeyId: string) => export const e2eRequestRoomKey = (rid: string, e2eKeyId: string): Promise<{ message: { msg?: string }; success: boolean }> =>
// RC 0.70.0 // RC 0.70.0
sdk.methodCallWrapper('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId); sdk.methodCallWrapper('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId);
export const updateJitsiTimeout = (roomId: string): any => export const updateJitsiTimeout = (roomId: string) =>
// RC 0.74.0 // RC 0.74.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post('video-conference/jitsi.update-timeout', { roomId }); sdk.post('video-conference/jitsi.update-timeout', { roomId });
export const register = (credentials: { name: string; email: string; pass: string; username: string }) => export const register = (credentials: { name: string; email: string; pass: string; username: string }) =>
@ -74,7 +73,7 @@ export const forgotPassword = (email: string) =>
export const sendConfirmationEmail = (email: string): Promise<{ message: string; success: boolean }> => export const sendConfirmationEmail = (email: string): Promise<{ message: string; success: boolean }> =>
sdk.methodCallWrapper('sendConfirmationEmail', email); sdk.methodCallWrapper('sendConfirmationEmail', email);
export const spotlight = (search: string, usernames: string, type: { users: boolean; rooms: boolean }) => export const spotlight = (search: string, usernames: string[], type: { users: boolean; rooms: boolean }): Promise<ISpotlight> =>
// RC 0.51.0 // RC 0.51.0
sdk.methodCallWrapper('spotlight', search, usernames, type); sdk.methodCallWrapper('spotlight', search, usernames, type);
@ -222,14 +221,11 @@ export const convertTeamToChannel = ({ teamId, selected }: { teamId: string; sel
return sdk.post('teams.convertToChannel', params); return sdk.post('teams.convertToChannel', params);
}; };
export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p'): any => { export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p') => {
// TODO: join code
// RC 0.48.0 // RC 0.48.0
if (type === 'p') { if (type === 'p') {
return sdk.methodCallWrapper('joinRoom', roomId); return sdk.methodCallWrapper('joinRoom', roomId) as Promise<boolean>;
} }
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('channels.join', { roomId, joinCode }); return sdk.post('channels.join', { roomId, joinCode });
}; };
@ -241,52 +237,38 @@ export const markAsUnread = ({ messageId }: { messageId: string }) =>
// RC 0.65.0 // RC 0.65.0
sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } }); sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
export const toggleStarMessage = (messageId: string, starred: boolean): any => { export const toggleStarMessage = (messageId: string, starred: boolean) => {
if (starred) { if (starred) {
// RC 0.59.0 // RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.unStarMessage', { messageId }); return sdk.post('chat.unStarMessage', { messageId });
} }
// RC 0.59.0 // RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.starMessage', { messageId }); return sdk.post('chat.starMessage', { messageId });
}; };
export const togglePinMessage = (messageId: string, pinned: boolean): any => { export const togglePinMessage = (messageId: string, pinned: boolean) => {
if (pinned) { if (pinned) {
// RC 0.59.0 // RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.unPinMessage', { messageId }); return sdk.post('chat.unPinMessage', { messageId });
} }
// RC 0.59.0 // RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.pinMessage', { messageId }); return sdk.post('chat.pinMessage', { messageId });
}; };
export const reportMessage = (messageId: string): any => export const reportMessage = (messageId: string) =>
// RC 0.64.0 // RC 0.64.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' }); sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
export const setUserPreferences = (userId: string, data: Partial<INotificationPreferences>) => export const setUserPreferences = (userId: string, data: Partial<INotificationPreferences>) =>
// RC 0.62.0 // RC 0.62.0
sdk.post('users.setPreferences', { userId, data }); sdk.post('users.setPreferences', { userId, data });
export const setUserStatus = (status?: string, message?: string): any => export const setUserStatus = (status: string, message: string) =>
// RC 1.2.0 // RC 1.2.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post('users.setStatus', { status, message }); sdk.post('users.setStatus', { status, message });
export const setReaction = (emoji: string, messageId: string): any => export const setReaction = (emoji: string, messageId: string) =>
// RC 0.62.2 // RC 0.62.2
// TODO: missing definitions from server
// @ts-ignore
sdk.post('chat.react', { emoji, messageId }); sdk.post('chat.react', { emoji, messageId });
export const toggleRead = (read: boolean, roomId: string) => { export const toggleRead = (read: boolean, roomId: string) => {
@ -296,20 +278,15 @@ export const toggleRead = (read: boolean, roomId: string) => {
return sdk.post('subscriptions.read', { rid: roomId }); return sdk.post('subscriptions.read', { rid: roomId });
}; };
export const getUserRoles = () => export const getRoomCounters = (
// RC 0.27.0 roomId: string,
sdk.methodCallWrapper('getUserRoles'); t: SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL
) =>
export const getRoomCounters = (roomId: string, t: RoomTypes): any =>
// RC 0.65.0 // RC 0.65.0
// TODO: missing definitions from server
// @ts-ignore
sdk.get(`${roomTypeToApiType(t)}.counters`, { roomId }); sdk.get(`${roomTypeToApiType(t)}.counters`, { roomId });
export const getChannelInfo = (roomId: string): any => export const getChannelInfo = (roomId: string) =>
// RC 0.48.0 // RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
sdk.get('channels.info', { roomId }); sdk.get('channels.info', { roomId });
export const getUserPreferences = (userId: string): any => export const getUserPreferences = (userId: string): any =>
@ -367,7 +344,7 @@ export const editLivechat = (userData: any, roomData: any) =>
// RC 0.55.0 // RC 0.55.0
sdk.methodCallWrapper('livechat:saveInfo', userData, roomData); sdk.methodCallWrapper('livechat:saveInfo', userData, roomData);
export const returnLivechat = (rid: string) => export const returnLivechat = (rid: string): Promise<boolean> =>
// RC 0.72.0 // RC 0.72.0
sdk.methodCallWrapper('livechat:returnAsInquiry', rid); sdk.methodCallWrapper('livechat:returnAsInquiry', rid);
@ -408,7 +385,13 @@ export const getRoutingConfig = (): Promise<{
// RC 2.0.0 // RC 2.0.0
sdk.methodCallWrapper('livechat:getRoutingConfig'); sdk.methodCallWrapper('livechat:getRoutingConfig');
export const getTagsList = () => export const getTagsList = (): Promise<
{
_id: string;
name: string;
departments: string[];
}[]
> =>
// RC 2.0.0 // RC 2.0.0
sdk.methodCallWrapper('livechat:getTagsList'); sdk.methodCallWrapper('livechat:getTagsList');
@ -422,7 +405,7 @@ export const getCustomFields = () =>
// RC 2.2.0 // RC 2.2.0
sdk.get('livechat/custom-fields'); sdk.get('livechat/custom-fields');
export const getListCannedResponse = ({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }): any => { export const getListCannedResponse = ({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }) => {
const params = { const params = {
offset, offset,
count, count,
@ -432,8 +415,6 @@ export const getListCannedResponse = ({ scope = '', departmentId = '', offset =
}; };
// RC 3.17.0 // RC 3.17.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.get('canned-responses', params); return sdk.get('canned-responses', params);
}; };
@ -446,19 +427,19 @@ export const toggleBlockUser = (rid: string, blocked: string, block: boolean): P
return sdk.methodCallWrapper('unblockUser', { rid, blocked }); return sdk.methodCallWrapper('unblockUser', { rid, blocked });
}; };
export const leaveRoom = (roomId: string, t: RoomTypes): any => export const leaveRoom = (roomId: string, t: RoomTypes) =>
// RC 0.48.0 // RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post(`${roomTypeToApiType(t)}.leave`, { roomId }); sdk.post(`${roomTypeToApiType(t)}.leave`, { roomId });
export const deleteRoom = (roomId: string, t: RoomTypes): any => export const deleteRoom = (roomId: string, t: RoomTypes) =>
// RC 0.49.0 // RC 0.49.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post(`${roomTypeToApiType(t)}.delete`, { roomId }); sdk.post(`${roomTypeToApiType(t)}.delete`, { roomId });
export const toggleMuteUserInRoom = (rid: string, username: string, mute: boolean) => { export const toggleMuteUserInRoom = (
rid: string,
username: string,
mute: boolean
): Promise<{ message: { msg: string; result: boolean }; success: boolean }> => {
if (mute) { if (mute) {
// RC 0.51.0 // RC 0.51.0
return sdk.methodCallWrapper('muteUserInRoom', { rid, username }); return sdk.methodCallWrapper('muteUserInRoom', { rid, username });
@ -477,17 +458,14 @@ export const toggleRoomOwner = ({
t: SubscriptionType; t: SubscriptionType;
userId: string; userId: string;
isOwner: boolean; isOwner: boolean;
}): any => { }) => {
const type = t as SubscriptionType.CHANNEL;
if (isOwner) { if (isOwner) {
// RC 0.49.4 // RC 0.49.4
// TODO: missing definitions from server return sdk.post(`${roomTypeToApiType(type)}.addOwner`, { roomId, userId });
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.addOwner`, { roomId, userId });
} }
// RC 0.49.4 // RC 0.49.4
// TODO: missing definitions from server return sdk.post(`${roomTypeToApiType(type)}.removeOwner`, { roomId, userId });
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.removeOwner`, { roomId, userId });
}; };
export const toggleRoomLeader = ({ export const toggleRoomLeader = ({
@ -500,17 +478,14 @@ export const toggleRoomLeader = ({
t: SubscriptionType; t: SubscriptionType;
userId: string; userId: string;
isLeader: boolean; isLeader: boolean;
}): any => { }) => {
const type = t as SubscriptionType.CHANNEL;
if (isLeader) { if (isLeader) {
// RC 0.58.0 // RC 0.58.0
// TODO: missing definitions from server return sdk.post(`${roomTypeToApiType(type)}.addLeader`, { roomId, userId });
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.addLeader`, { roomId, userId });
} }
// RC 0.58.0 // RC 0.58.0
// TODO: missing definitions from server return sdk.post(`${roomTypeToApiType(type)}.removeLeader`, { roomId, userId });
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.removeLeader`, { roomId, userId });
}; };
export const toggleRoomModerator = ({ export const toggleRoomModerator = ({
@ -523,29 +498,22 @@ export const toggleRoomModerator = ({
t: SubscriptionType; t: SubscriptionType;
userId: string; userId: string;
isModerator: boolean; isModerator: boolean;
}): any => { }) => {
const type = t as SubscriptionType.CHANNEL;
if (isModerator) { if (isModerator) {
// RC 0.49.4 // RC 0.49.4
// TODO: missing definitions from server return sdk.post(`${roomTypeToApiType(type)}.addModerator`, { roomId, userId });
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.addModerator`, { roomId, userId });
} }
// RC 0.49.4 // RC 0.49.4
// TODO: missing definitions from server return sdk.post(`${roomTypeToApiType(type)}.removeModerator`, { roomId, userId });
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.removeModerator`, { roomId, userId });
}; };
export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: SubscriptionType; userId: string }): any => export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomTypes; userId: string }) =>
// RC 0.48.0 // RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post(`${roomTypeToApiType(t)}.kick`, { roomId, userId }); sdk.post(`${roomTypeToApiType(t)}.kick`, { roomId, userId });
export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: string; ignore: boolean }): any => export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: string; ignore: boolean }) =>
// RC 0.64.0 // RC 0.64.0
// TODO: missing definitions from server
// @ts-ignore
sdk.get('chat.ignoreUser', { rid, userId, ignore }); sdk.get('chat.ignoreUser', { rid, userId, ignore });
export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: boolean) => { export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: boolean) => {
@ -558,10 +526,8 @@ export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive:
return sdk.post(`${roomTypeToApiType(type)}.unarchive`, { roomId }); return sdk.post(`${roomTypeToApiType(type)}.unarchive`, { roomId });
}; };
export const hideRoom = (roomId: string, t: RoomTypes): any => export const hideRoom = (roomId: string, t: RoomTypes) =>
// RC 0.48.0 // RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post(`${roomTypeToApiType(t)}.close`, { roomId }); sdk.post(`${roomTypeToApiType(t)}.close`, { roomId });
export const saveRoomSettings = ( export const saveRoomSettings = (

View File

@ -33,8 +33,7 @@ const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }:
styles.centerContainer, styles.centerContainer,
{ {
borderColor: themes[theme].separatorColor borderColor: themes[theme].separatorColor
}, }
displayMode === DisplayMode.Condensed && styles.condensedPaddingVertical
]}> ]}>
{children} {children}
</View> </View>

View File

@ -21,9 +21,6 @@ export default StyleSheet.create<any>({
containerCondensed: { containerCondensed: {
height: ROW_HEIGHT_CONDENSED height: ROW_HEIGHT_CONDENSED
}, },
condensedPaddingVertical: {
paddingVertical: 20
},
centerContainer: { centerContainer: {
flex: 1, flex: 1,
paddingVertical: 10, paddingVertical: 10,
@ -33,7 +30,6 @@ export default StyleSheet.create<any>({
title: { title: {
flex: 1, flex: 1,
fontSize: 17, fontSize: 17,
lineHeight: 20,
...sharedStyles.textMedium ...sharedStyles.textMedium
}, },
alert: { alert: {
@ -67,7 +63,6 @@ export default StyleSheet.create<any>({
markdownText: { markdownText: {
flex: 1, flex: 1,
fontSize: 14, fontSize: 14,
lineHeight: 17,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
avatar: { avatar: {

View File

@ -47,15 +47,6 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) {
} }
} }
// Force fetch all subscriptions to update columns related to Teams feature
// TODO: remove it a couple of releases
const teamsMigrationKey = `${server}_TEAMS_MIGRATION`;
const teamsMigration = yield UserPreferences.getBoolAsync(teamsMigrationKey);
if (!teamsMigration) {
roomsUpdatedAt = null;
UserPreferences.setBoolAsync(teamsMigrationKey, true);
}
const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt); const [subscriptionsResult, roomsResult] = yield RocketChat.getRooms(roomsUpdatedAt);
const subscriptions = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult); const subscriptions = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
const db = database.active; const db = database.active;

View File

@ -1,7 +1,7 @@
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { ISubscription, IVisitor, SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription'; import { IOmnichannelRoom, SubscriptionType, IVisitor, TSubscriptionModel, ISubscription } from '../definitions';
interface IGoRoomItem { interface IGoRoomItem {
search?: boolean; // comes from spotlight search?: boolean; // comes from spotlight
@ -13,7 +13,7 @@ interface IGoRoomItem {
visitor?: IVisitor; visitor?: IVisitor;
} }
export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription; export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription | IOmnichannelRoomVisitor;
const navigate = ({ const navigate = ({
item, item,
@ -42,6 +42,11 @@ const navigate = ({
}); });
}; };
interface IOmnichannelRoomVisitor extends IOmnichannelRoom {
// this visitor came from ee/omnichannel/views/QueueListView
visitor: IVisitor;
}
export const goRoom = async ({ export const goRoom = async ({
item, item,
isMasterDetail = false, isMasterDetail = false,

View File

@ -1,15 +1,15 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Text } from 'react-native'; 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 { themes } from '../../constants/colors';
import { TSubscriptionModel } from '../../definitions/ISubscription'; import { MultiSelect } from '../../containers/UIKit/MultiSelect';
import styles from './styles'; 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 { ICreateDiscussionViewSelectChannel } from './interfaces';
import styles from './styles';
const SelectChannel = ({ const SelectChannel = ({
server, server,
@ -21,7 +21,7 @@ const SelectChannel = ({
serverVersion, serverVersion,
theme theme
}: ICreateDiscussionViewSelectChannel): JSX.Element => { }: ICreateDiscussionViewSelectChannel): JSX.Element => {
const [channels, setChannels] = useState<TSubscriptionModel[]>([]); const [channels, setChannels] = useState<ISearchLocal[]>([]);
const getChannels = debounce(async (keyword = '') => { const getChannels = debounce(async (keyword = '') => {
try { try {

View File

@ -20,10 +20,6 @@ const styles = StyleSheet.create({
padding: 16 padding: 16
} }
}); });
interface IUser {
username: string;
_id: string;
}
interface IParsedData { interface IParsedData {
label: string; label: string;
@ -47,7 +43,7 @@ const ForwardLivechatView = ({ navigation, route, theme }: IBaseScreen<ChatsStac
try { try {
const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset }); const result = await RocketChat.getDepartments({ count: COUNT_DEPARTMENT, text, offset });
if (result.success) { if (result.success) {
const parsedDepartments: IParsedData[] = result.departments.map(department => ({ const parsedDepartments = result.departments.map(department => ({
label: department.name, label: department.name,
value: department._id value: department._id
})); }));
@ -71,7 +67,7 @@ const ForwardLivechatView = ({ navigation, route, theme }: IBaseScreen<ChatsStac
term term
}); });
if (result.success) { if (result.success) {
const parsedUsers = result.items.map((user: IUser) => ({ label: user.username, value: user._id })); const parsedUsers = result.items.map(user => ({ label: user.username, value: user._id }));
if (!term) { if (!term) {
setUsers(parsedUsers); setUsers(parsedUsers);
} }

View File

@ -19,7 +19,6 @@ import Button from '../containers/Button';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { MultiSelect } from '../containers/UIKit/MultiSelect'; import { MultiSelect } from '../containers/UIKit/MultiSelect';
import { ILivechatVisitor } from '../definitions/ILivechatVisitor'; import { ILivechatVisitor } from '../definitions/ILivechatVisitor';
import { ITagsOmnichannel } from '../definitions/ITagsOmnichannel';
import { IApplicationState, ISubscription } from '../definitions'; import { IApplicationState, ISubscription } from '../definitions';
import { ChatsStackParamList } from '../stacks/types'; import { ChatsStackParamList } from '../stacks/types';
import sharedStyles from './Styles'; import sharedStyles from './Styles';
@ -48,12 +47,6 @@ interface ITitle {
theme: string; theme: string;
} }
interface IField {
_id: string;
visibility: string;
scope: string;
}
interface IInputs { interface IInputs {
livechatData: { livechatData: {
[key: string]: any; [key: string]: any;
@ -116,17 +109,17 @@ const LivechatEditView = ({
const visitor = route.params?.roomUser ?? {}; const visitor = route.params?.roomUser ?? {};
const getCustomFields = async () => { const getCustomFields = async () => {
const result: any = await RocketChat.getCustomFields(); const result = await RocketChat.getCustomFields();
if (result.success && result.customFields?.length) { if (result.success && result.customFields?.length) {
const visitorCustomFields = result.customFields const visitorCustomFields = result.customFields
.filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'visitor') .filter(field => field.visibility !== 'hidden' && field.scope === 'visitor')
.map((field: IField) => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' })) .map(field => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' }))
.reduce((ret: IField, field: IField) => ({ ...field, ...ret })); .reduce((ret, field) => ({ ...field, ...ret }), {});
const livechatCustomFields = result.customFields const livechatCustomFields = result.customFields
.filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'room') .filter(field => field.visibility !== 'hidden' && field.scope === 'room')
.map((field: IField) => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' })) .map(field => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' }))
.reduce((ret: IField, field: IField) => ({ ...field, ...ret })); .reduce((ret, field) => ({ ...field, ...ret }), {});
return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields }); return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields });
} }
@ -142,7 +135,7 @@ const LivechatEditView = ({
}, [availableUserTags]); }, [availableUserTags]);
const getTagsList = async (agentDepartments: string[]) => { const getTagsList = async (agentDepartments: string[]) => {
const tags: ITagsOmnichannel[] = await RocketChat.getTagsList(); const tags = await RocketChat.getTagsList();
const isAdmin = ['admin', 'livechat-manager'].find(role => user.roles.includes(role)); const isAdmin = ['admin', 'livechat-manager'].find(role => user.roles.includes(role));
const availableTags = tags const availableTags = tags
.filter(({ departments }) => isAdmin || departments.length === 0 || departments.some(i => agentDepartments.indexOf(i) > -1)) .filter(({ departments }) => isAdmin || departments.length === 0 || departments.some(i => agentDepartments.indexOf(i) > -1))

View File

@ -12,7 +12,7 @@ import * as List from '../containers/List';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import SearchBox from '../containers/SearchBox'; import SearchBox from '../containers/SearchBox';
import StatusBar from '../containers/StatusBar'; import StatusBar from '../containers/StatusBar';
import { IApplicationState, IBaseScreen, TSubscriptionModel } from '../definitions'; import { IApplicationState, IBaseScreen, ISearch, TSubscriptionModel } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import database from '../lib/database'; import database from '../lib/database';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
@ -55,18 +55,6 @@ interface IButton {
first?: boolean; first?: boolean;
} }
interface ISearch {
_id: string;
status: string;
username: string;
avatarETag: string;
outside: boolean;
rid: string;
name: string;
t: string;
search: boolean;
}
interface INewMessageViewState { interface INewMessageViewState {
search: (ISearch | TSubscriptionModel)[]; search: (ISearch | TSubscriptionModel)[];
chats: TSubscriptionModel[]; chats: TSubscriptionModel[];
@ -149,7 +137,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
}; };
search = async (text: string) => { search = async (text: string) => {
const result: ISearch[] | TSubscriptionModel[] = await RocketChat.search({ text, filterRooms: false }); const result = (await RocketChat.search({ text, filterRooms: false })) as ISearch[];
this.setState({ this.setState({
search: result search: result
}); });
@ -313,7 +301,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
<FlatList <FlatList
data={search.length > 0 ? search : chats} data={search.length > 0 ? search : chats}
extraData={this.state} extraData={this.state}
keyExtractor={item => item._id} keyExtractor={item => item._id || item.rid}
ListHeaderComponent={this.renderHeader} ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem} renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator} ItemSeparatorComponent={List.Separator}

View File

@ -106,7 +106,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
this.t = props.route.params?.t; this.t = props.route.params?.t;
this.joined = props.route.params?.joined; this.joined = props.route.params?.joined;
this.state = { this.state = {
room: room || ({ rid: this.rid, t: this.t } as any), room: room || { rid: this.rid, t: this.t },
membersCount: 0, membersCount: 0,
member: member || {}, member: member || {},
joined: !!room, joined: !!room,
@ -144,6 +144,7 @@ class RoomActionsView extends React.Component<IRoomActionsViewProps, IRoomAction
try { try {
const result = await RocketChat.getChannelInfo(room.rid); const result = await RocketChat.getChannelInfo(room.rid);
if (result.success) { if (result.success) {
// @ts-ignore
this.setState({ room: { ...result.channel, rid: result.channel._id } }); this.setState({ room: { ...result.channel, rid: result.channel._id } });
} }
} catch (e) { } catch (e) {

View File

@ -28,6 +28,7 @@ import { goRoom, TGoRoomItem } from '../../utils/goRoom';
import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import log from '../../utils/log'; import log from '../../utils/log';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { RoomTypes } from '../../lib/rocketchat/methods/roomTypeToApiType';
import styles from './styles'; import styles from './styles';
const PAGE_SIZE = 25; const PAGE_SIZE = 25;
@ -590,7 +591,8 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
try { try {
const { room, members, membersFiltered } = this.state; const { room, members, membersFiltered } = this.state;
const userId = selectedUser._id; const userId = selectedUser._id;
await RocketChat.removeUserFromRoom({ roomId: room.rid, t: room.t, userId }); // TODO: interface SubscriptionType on IRoom is wrong
await RocketChat.removeUserFromRoom({ roomId: room.rid, t: room.t as RoomTypes, userId });
const message = I18n.t('User_has_been_removed_from_s', { s: RocketChat.getRoomTitle(room) }); const message = I18n.t('User_has_been_removed_from_s', { s: RocketChat.getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message }); EventEmitter.emit(LISTENER, { message });
this.setState({ this.setState({

View File

@ -11,7 +11,7 @@ import SafeAreaView from '../containers/SafeAreaView';
import Status from '../containers/Status/Status'; import Status from '../containers/Status/Status';
import TextInput from '../containers/TextInput'; import TextInput from '../containers/TextInput';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import { IApplicationState, IBaseScreen } from '../definitions'; import { IApplicationState, IBaseScreen, IUser } from '../definitions';
import I18n from '../i18n'; import I18n from '../i18n';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import { getUserSelector } from '../selectors/login'; import { getUserSelector } from '../selectors/login';
@ -54,19 +54,13 @@ const styles = StyleSheet.create({
} }
}); });
interface IUser {
id?: string;
status?: string;
statusText?: string;
}
interface IStatusViewState { interface IStatusViewState {
statusText: string; statusText: string;
loading: boolean; loading: boolean;
} }
interface IStatusViewProps extends IBaseScreen<any, 'StatusView'> { interface IStatusViewProps extends IBaseScreen<any, 'StatusView'> {
user: IUser; user: Pick<IUser, 'id' | 'status' | 'statusText'>;
isMasterDetail: boolean; isMasterDetail: boolean;
Accounts_AllowInvisibleStatusOption: boolean; Accounts_AllowInvisibleStatusOption: boolean;
} }

View File

@ -1,15 +0,0 @@
export interface ILivechatDepartment {
_id: string;
name: string;
enabled: boolean;
description: string;
showOnRegistration: boolean;
showOnOfflineForm: boolean;
requestTagBeforeClosingChat: boolean;
email: string;
chatClosingTags: string[];
offlineMessageChannelName: string;
numAgents: number;
_updatedAt?: Date;
businessHourId?: string;
}

View File

@ -28,7 +28,7 @@ Or
* If you're running your own Rocket.Chat server, ensure it's started (e.g. `meteor npm start` in the server project directory). * If you're running your own Rocket.Chat server, ensure it's started (e.g. `meteor npm start` in the server project directory).
* Edit `e2e/data.js`: * Edit `e2e/data.js`:
* Set the `server` to the address of the server under test * Set the `server` to the address of the server under test
* Set the `adminUser` and `adminPassword` to an admin user on that environment (or a user with at least `create-user` and `create-c`). * Create a file called `e2e_account.js`, in the same folder as `data.js`. Set the `adminUser` and `adminPassword` to an admin user on that environment (or a user with at least `create-user` and `create-c` permissions). The example of how to create this file is on `e2e/e2e_account.example.js`
* Working example configs exist in `./e2e/data/`. Setting `FORCE_DEFAULT_DOCKER_DATA` to `1` in the `runTestsInDocker.sh` script will use the example config automatically * Working example configs exist in `./e2e/data/`. Setting `FORCE_DEFAULT_DOCKER_DATA` to `1` in the `runTestsInDocker.sh` script will use the example config automatically
### 3. Running tests ### 3. Running tests

View File

@ -1,10 +1,11 @@
const random = require('./helpers/random'); const random = require('./helpers/random');
// eslint-disable-next-line import/no-unresolved
const account = require('./e2e_account');
const value = random(20); const value = random(20);
const data = { const data = {
server: 'https://mobile.rocket.chat', server: 'https://mobile.rocket.chat',
adminUser: 'e2e_admin', ...account,
adminPassword: 'p7mFh4yLwCRXSnMvG',
alternateServer: 'https://stable.rocket.chat', alternateServer: 'https://stable.rocket.chat',
users: { users: {
regular: { regular: {

View File

@ -1,11 +1,12 @@
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
const random = require('./helpers/random'); const random = require('./helpers/random');
// eslint-disable-next-line import/no-unresolved
const account = require('./e2e_account');
const value = random(20); const value = random(20);
const data = { const data = {
server: 'https://mobile.rocket.chat', server: 'https://mobile.rocket.chat',
adminUser: 'e2e_admin', ...account,
adminPassword: 'p7mFh4yLwCRXSnMvG',
alternateServer: 'https://stable.rocket.chat', alternateServer: 'https://stable.rocket.chat',
users: { users: {
regular: { regular: {

View File

@ -0,0 +1,6 @@
const account = {
adminUser: 'Change_here',
adminPassword: 'Change_here'
};
module.exports = account;

File diff suppressed because one or more lines are too long