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/
e2e/docker/rc_test_env/docker-compose.yml
e2e/docker/data/db
e2e/e2e_account.js
*.p8

View File

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

View File

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

View File

@ -4,7 +4,7 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
import { IAttachment } from './IAttachment';
import { IMessage } from './IMessage';
import { IServedBy } from './IServedBy';
import { SubscriptionType } from './ISubscription';
import { IVisitor, SubscriptionType } from './ISubscription';
import { IUser } from './IUser';
interface IRequestTranscript {
@ -28,11 +28,8 @@ export interface IRoom {
broadcast: boolean;
encrypted: boolean;
ro: boolean;
v?: {
_id?: string;
token?: string;
status: 'online' | 'busy' | 'away' | 'offline';
};
v?: IVisitor;
status?: string;
servedBy?: IServedBy;
departmentId?: string;
livechatData?: any;
@ -55,13 +52,11 @@ export enum OmnichannelSourceType {
API = 'api',
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;
v: {
_id?: string;
token?: string;
status: 'online' | 'busy' | 'away' | 'offline';
};
v: IVisitor;
email?: {
// Data used when the room is created from an email, via email Integration.
inbox: string;
@ -83,6 +78,8 @@ export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | '
sidebarIcon?: string;
// The default sidebar icon
defaultIcon?: string;
_updatedAt?: Date;
queuedAt?: Date;
};
transcriptRequest?: IRequestTranscript;
servedBy?: IServedBy;
@ -91,18 +88,22 @@ export interface IOmnichannelRoom extends Omit<IRoom, 'default' | 'featured' | '
lastMessage?: IMessage & { token?: string };
tags: any;
closedAt: any;
metrics: any;
waitingResponse: any;
responseBy: any;
priorityId: any;
livechatData: any;
tags?: string[];
closedAt?: Date;
metrics?: any;
waitingResponse?: any;
responseBy?: any;
priorityId?: any;
livechatData?: any;
queuedAt?: Date;
ts: Date;
label?: string;
crmData?: unknown;
message?: string;
queueOrder?: string;
estimatedWaitingTimeQueue?: string;
estimatedServiceTimeAt?: Date;
}
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 {
_id: string;
username: string;
token: string;
status: string;
lastMessageTs: Date;
_id?: string;
token?: string;
status: 'online' | 'busy' | 'away' | 'offline';
username?: string;
lastMessageTs?: Date;
}
export enum ERoomTypes {
@ -76,7 +76,7 @@ export interface ISubscription {
jitsiTimeout?: number;
autoTranslate?: boolean;
autoTranslateLanguage?: string;
lastMessage?: ILastMessage; // TODO: we need to use IMessage here
lastMessage?: ILastMessage | null; // TODO: we need to use IMessage here
hideUnreadStatus?: boolean;
sysMes?: string[] | boolean;
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[];
replies?: string[];
tcount?: number;
status?: string;
status?: number;
tlm?: string | Date;
}

View File

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

View File

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

View File

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

View File

@ -52,4 +52,51 @@ export type ChannelsEndpoints = {
'channels.convertToTeam': {
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': {
POST: (params: { mid: IMessage['_id'] }) => void;
};
'chat.unStarMessage': {
POST: (params: { messageId: IMessage['_id'] }) => void;
};
'chat.starMessage': {
POST: (params: { messageId: IMessage['_id'] }) => void;
};
'chat.unfollowMessage': {
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': {
GET: (params: { roomId: IRoom['_id']; text?: string; offset: number; count: number }) => {
messages: IMessage[];
@ -39,4 +54,10 @@ export type ChatEndpoints = {
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': {
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[];
};
};
'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 { E2eEndpoints } from './e2e';
import { SubscriptionsEndpoints } from './subscriptions';
import { VideoConferenceEndpoints } from './videoConference';
export type Endpoints = ChannelsEndpoints &
ChatEndpoints &
@ -32,4 +33,5 @@ export type Endpoints = ChannelsEndpoints &
UsersEndpoints &
TeamsEndpoints &
E2eEndpoints &
SubscriptionsEndpoints;
SubscriptionsEndpoints &
VideoConferenceEndpoints;

View File

@ -1,3 +1,4 @@
import { ICannedResponse } from '../../ICannedResponse';
import { ILivechatAgent } from '../../ILivechatAgent';
import { ILivechatDepartment } from '../../ILivechatDepartment';
import { ILivechatDepartmentAgents } from '../../ILivechatDepartmentAgents';
@ -102,6 +103,8 @@ export type OmnichannelEndpoints = {
{
_id: string;
label: string;
visibility?: string;
scope?: string;
}
];
}>;
@ -190,4 +193,10 @@ export type OmnichannelEndpoints = {
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': {
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 { Switch, View } from 'react-native';
import PropTypes from 'prop-types';
import * as List from '../../../containers/List';
import styles from '../../../views/RoomsListView/styles';
import { SWITCH_TRACK_COLOR, themes } from '../../../constants/colors';
import { withTheme } from '../../../theme';
import { useTheme } from '../../../theme';
import UnreadBadge from '../../../presentation/UnreadBadge';
import RocketChat from '../../../lib/rocketchat';
import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib';
import { IUser } from '../../../definitions/IUser';
const OmnichannelStatus = memo(({ searching, goQueue, theme, queueSize, inquiryEnabled, user }) => {
if (searching > 0 || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) {
interface IOmnichannelStatus {
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;
}
const { theme } = useTheme();
const [status, setStatus] = useState(isOmnichannelStatusAvailable(user));
useEffect(() => {
@ -48,16 +57,4 @@ const OmnichannelStatus = memo(({ searching, goQueue, theme, queueSize, inquiryE
);
});
OmnichannelStatus.propTypes = {
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);
export default 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 subscribeInquiry from './subscriptions/inquiry';
export const isOmnichannelStatusAvailable = user => user?.statusLivechat === 'available';
export const isOmnichannelStatusAvailable = (user: IUser): boolean => user?.statusLivechat === 'available';
// RC 0.26.0
export const changeLivechatStatus = () => RocketChat.methodCallWrapper('livechat:changeLivechatStatus');
export const changeLivechatStatus = () => sdk.methodCallWrapper('livechat:changeLivechatStatus');
// 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
// and will be removed by the queue stream
// 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 {
private inquirySub: { stop: () => void } | null;
constructor() {
this.inquirySub = null;
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();

View File

@ -2,11 +2,28 @@ import log from '../../../../utils/log';
import { store } from '../../../../lib/auxStore';
import RocketChat from '../../../../lib/rocketchat';
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;
let queueListener;
interface IDdpMessage {
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';
@ -15,7 +32,7 @@ export default function subscribeInquiry() {
store.dispatch(inquiryRequest());
};
const handleQueueMessageReceived = ddpMessage => {
const handleQueueMessageReceived = (ddpMessage: IDdpMessage) => {
const [{ type, ...sub }] = ddpMessage.fields.args;
// 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 (sub.status !== 'queued') {
// remove it from the queue
if (sub._id) {
store.dispatch(inquiryQueueRemove(sub._id));
}
return;
}
@ -53,23 +72,28 @@ export default function subscribeInquiry() {
}
};
connectedListener = RocketChat.onStreamData('connected', handleConnection);
queueListener = RocketChat.onStreamData(streamTopic, handleQueueMessageReceived);
connectedListener = sdk.onStreamData('connected', handleConnection);
queueListener = sdk.onStreamData(streamTopic, handleQueueMessageReceived);
try {
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) {
const { departments } = result;
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);
departmentIds.forEach(departmentId => {
// 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';
const initialState = {
export interface IInquiry {
enabled: boolean;
queued: IOmnichannelRoom[];
error: any;
}
export const initialState: IInquiry = {
enabled: false,
queued: [],
error: {}
};
export default function inquiry(state = initialState, action) {
export default function inquiry(state = initialState, action: TApplicationActions): IInquiry {
switch (action.type) {
case INQUIRY.SUCCESS:
return {

View File

@ -1,5 +1,7 @@
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);

View File

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList } from 'react-native';
import { CompositeNavigationProp } from '@react-navigation/native';
import { StackNavigationOptions, StackNavigationProp } from '@react-navigation/stack';
import { FlatList, ListRenderItem } from 'react-native';
import { connect } from 'react-redux';
import { dequal } from 'dequal';
@ -19,18 +20,50 @@ import * as HeaderButton from '../../../containers/HeaderButton';
import RocketChat from '../../../lib/rocketchat';
import { events, logEvent } from '../../../utils/log';
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 getItemLayout = (data, index) => ({
const getItemLayout = (data: IOmnichannelRoom[] | null | undefined, index: number) => ({
length: ROW_HEIGHT,
offset: ROW_HEIGHT * index,
index
});
const keyExtractor = item => item.rid;
const keyExtractor = (item: IOmnichannelRoom) => item.rid;
class QueueListView extends React.Component {
static navigationOptions = ({ navigation, isMasterDetail }) => {
const options = {
class QueueListView extends React.Component<IQueueListView, any> {
private getScrollRef?: React.Ref<FlatList<IOmnichannelRoom>>;
private onEndReached: ((info: { distanceFromEnd: number }) => void) | null | undefined;
static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => {
const options: StackNavigationOptions = {
title: I18n.t('Queued_chats')
};
if (isMasterDetail) {
@ -39,24 +72,7 @@ class QueueListView extends React.Component {
return options;
};
static propTypes = {
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) {
shouldComponentUpdate(nextProps: IQueueListView) {
const { queued } = this.props;
if (!dequal(nextProps.queued, queued)) {
return true;
@ -65,7 +81,7 @@ class QueueListView extends React.Component {
return false;
}
onPressItem = (item = {}) => {
onPressItem = (item = {} as IOmnichannelRoom) => {
logEvent(events.QL_GO_ROOM);
const { navigation, isMasterDetail } = this.props;
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 {
user: { id: userId, username, token },
server,
@ -152,7 +168,7 @@ class QueueListView extends React.Component {
}
}
const mapStateToProps = state => ({
const mapStateToProps = (state: IApplicationState) => ({
user: getUserSelector(state),
isMasterDetail: state.app.isMasterDetail,
server: state.server.server,

View File

@ -4,7 +4,10 @@ import { MESSAGES_TABLE } from '../model/Message';
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 messageCollection = getCollection(db);
try {

View File

@ -2,7 +2,7 @@ import { ILastMessage, IMessage, IThreadResult } from '../../../definitions';
import messagesStatus from '../../../constants/messagesStatus';
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;
return normalizeMessage(message);
};

View File

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

View File

@ -7,7 +7,7 @@ import log from '../../utils/log';
import random from '../../utils/random';
import { Encryption } from '../encryption';
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';
const changeMessageStatus = async (id: string, status: number, tmid?: string, message?: IMessage) => {
@ -145,7 +145,7 @@ export default async function (
tm.u = tMessageRecord.u;
tm.t = message.t;
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;
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;
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)) {
const lastMessage = buildMessage(tmp.lastMessage);
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) {
batch.push(
@ -206,10 +209,12 @@ const createOrUpdateSubscription = async (subscription: ISubscription, room: IRo
} else {
batch.push(
messagesCollection.prepareCreate(m => {
if (lastMessage) {
m._raw = sanitizedRaw({ id: lastMessage._id }, messagesCollection.schema);
if (m.subscription) {
m.subscription.id = lastMessage.rid;
}
}
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 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';
@ -85,6 +86,7 @@ const RocketChat = {
CURRENT_SERVER,
CERTIFICATE_KEY,
...restAPis,
...search,
callJitsi,
callJitsiWithoutServer,
async subscribeRooms() {
@ -252,93 +254,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(',');

View File

@ -2,6 +2,7 @@ import sdk from './sdk';
import { TEAM_TYPE } from '../../../definitions/ITeam';
import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType';
import { SubscriptionType, INotificationPreferences } from '../../../definitions';
import { ISpotlight } from '../../../definitions/ISpotlight';
export const createChannel = ({
name,
@ -53,14 +54,12 @@ export const e2eUpdateGroupKey = (uid: string, rid: string, key: string): any =>
// RC 0.70.0
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
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
// TODO: missing definitions from server
// @ts-ignore
sdk.post('video-conference/jitsi.update-timeout', { roomId });
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 }> =>
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
sdk.methodCallWrapper('spotlight', search, usernames, type);
@ -222,14 +221,11 @@ export const convertTeamToChannel = ({ teamId, selected }: { teamId: string; sel
return sdk.post('teams.convertToChannel', params);
};
export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p'): any => {
// TODO: join code
export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p') => {
// RC 0.48.0
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 });
};
@ -241,52 +237,38 @@ export const markAsUnread = ({ messageId }: { messageId: string }) =>
// RC 0.65.0
sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } });
export const toggleStarMessage = (messageId: string, starred: boolean): any => {
export const toggleStarMessage = (messageId: string, starred: boolean) => {
if (starred) {
// RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.unStarMessage', { messageId });
}
// RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.starMessage', { messageId });
};
export const togglePinMessage = (messageId: string, pinned: boolean): any => {
export const togglePinMessage = (messageId: string, pinned: boolean) => {
if (pinned) {
// RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.unPinMessage', { messageId });
}
// RC 0.59.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post('chat.pinMessage', { messageId });
};
export const reportMessage = (messageId: string): any =>
export const reportMessage = (messageId: string) =>
// RC 0.64.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' });
export const setUserPreferences = (userId: string, data: Partial<INotificationPreferences>) =>
// RC 0.62.0
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
// TODO: missing definitions from server
// @ts-ignore
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
// TODO: missing definitions from server
// @ts-ignore
sdk.post('chat.react', { emoji, messageId });
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 });
};
export const getUserRoles = () =>
// RC 0.27.0
sdk.methodCallWrapper('getUserRoles');
export const getRoomCounters = (roomId: string, t: RoomTypes): any =>
export const getRoomCounters = (
roomId: string,
t: SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL
) =>
// RC 0.65.0
// TODO: missing definitions from server
// @ts-ignore
sdk.get(`${roomTypeToApiType(t)}.counters`, { roomId });
export const getChannelInfo = (roomId: string): any =>
export const getChannelInfo = (roomId: string) =>
// RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
sdk.get('channels.info', { roomId });
export const getUserPreferences = (userId: string): any =>
@ -367,7 +344,7 @@ export const editLivechat = (userData: any, roomData: any) =>
// RC 0.55.0
sdk.methodCallWrapper('livechat:saveInfo', userData, roomData);
export const returnLivechat = (rid: string) =>
export const returnLivechat = (rid: string): Promise<boolean> =>
// RC 0.72.0
sdk.methodCallWrapper('livechat:returnAsInquiry', rid);
@ -408,7 +385,13 @@ export const getRoutingConfig = (): Promise<{
// RC 2.0.0
sdk.methodCallWrapper('livechat:getRoutingConfig');
export const getTagsList = () =>
export const getTagsList = (): Promise<
{
_id: string;
name: string;
departments: string[];
}[]
> =>
// RC 2.0.0
sdk.methodCallWrapper('livechat:getTagsList');
@ -422,7 +405,7 @@ export const getCustomFields = () =>
// RC 2.2.0
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 = {
offset,
count,
@ -432,8 +415,6 @@ export const getListCannedResponse = ({ scope = '', departmentId = '', offset =
};
// RC 3.17.0
// TODO: missing definitions from server
// @ts-ignore
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 });
};
export const leaveRoom = (roomId: string, t: RoomTypes): any =>
export const leaveRoom = (roomId: string, t: RoomTypes) =>
// RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
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
// TODO: missing definitions from server
// @ts-ignore
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) {
// RC 0.51.0
return sdk.methodCallWrapper('muteUserInRoom', { rid, username });
@ -477,17 +458,14 @@ export const toggleRoomOwner = ({
t: SubscriptionType;
userId: string;
isOwner: boolean;
}): any => {
}) => {
const type = t as SubscriptionType.CHANNEL;
if (isOwner) {
// RC 0.49.4
// TODO: missing definitions from server
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.addOwner`, { roomId, userId });
return sdk.post(`${roomTypeToApiType(type)}.addOwner`, { roomId, userId });
}
// RC 0.49.4
// TODO: missing definitions from server
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.removeOwner`, { roomId, userId });
return sdk.post(`${roomTypeToApiType(type)}.removeOwner`, { roomId, userId });
};
export const toggleRoomLeader = ({
@ -500,17 +478,14 @@ export const toggleRoomLeader = ({
t: SubscriptionType;
userId: string;
isLeader: boolean;
}): any => {
}) => {
const type = t as SubscriptionType.CHANNEL;
if (isLeader) {
// RC 0.58.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.addLeader`, { roomId, userId });
return sdk.post(`${roomTypeToApiType(type)}.addLeader`, { roomId, userId });
}
// RC 0.58.0
// TODO: missing definitions from server
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.removeLeader`, { roomId, userId });
return sdk.post(`${roomTypeToApiType(type)}.removeLeader`, { roomId, userId });
};
export const toggleRoomModerator = ({
@ -523,29 +498,22 @@ export const toggleRoomModerator = ({
t: SubscriptionType;
userId: string;
isModerator: boolean;
}): any => {
}) => {
const type = t as SubscriptionType.CHANNEL;
if (isModerator) {
// RC 0.49.4
// TODO: missing definitions from server
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.addModerator`, { roomId, userId });
return sdk.post(`${roomTypeToApiType(type)}.addModerator`, { roomId, userId });
}
// RC 0.49.4
// TODO: missing definitions from server
// @ts-ignore
return sdk.post(`${roomTypeToApiType(t)}.removeModerator`, { roomId, userId });
return sdk.post(`${roomTypeToApiType(type)}.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
// TODO: missing definitions from server
// @ts-ignore
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
// TODO: missing definitions from server
// @ts-ignore
sdk.get('chat.ignoreUser', { rid, userId, ignore });
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 });
};
export const hideRoom = (roomId: string, t: RoomTypes): any =>
export const hideRoom = (roomId: string, t: RoomTypes) =>
// RC 0.48.0
// TODO: missing definitions from server
// @ts-ignore
sdk.post(`${roomTypeToApiType(t)}.close`, { roomId });
export const saveRoomSettings = (

View File

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

View File

@ -21,9 +21,6 @@ export default StyleSheet.create<any>({
containerCondensed: {
height: ROW_HEIGHT_CONDENSED
},
condensedPaddingVertical: {
paddingVertical: 20
},
centerContainer: {
flex: 1,
paddingVertical: 10,
@ -33,7 +30,6 @@ export default StyleSheet.create<any>({
title: {
flex: 1,
fontSize: 17,
lineHeight: 20,
...sharedStyles.textMedium
},
alert: {
@ -67,7 +63,6 @@ export default StyleSheet.create<any>({
markdownText: {
flex: 1,
fontSize: 14,
lineHeight: 17,
...sharedStyles.textRegular
},
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 subscriptions = yield mergeSubscriptionsRooms(subscriptionsResult, roomsResult);
const db = database.active;

View File

@ -1,7 +1,7 @@
import { ChatsStackParamList } from '../stacks/types';
import Navigation from '../lib/Navigation';
import RocketChat from '../lib/rocketchat';
import { ISubscription, IVisitor, SubscriptionType, TSubscriptionModel } from '../definitions/ISubscription';
import { IOmnichannelRoom, SubscriptionType, IVisitor, TSubscriptionModel, ISubscription } from '../definitions';
interface IGoRoomItem {
search?: boolean; // comes from spotlight
@ -13,7 +13,7 @@ interface IGoRoomItem {
visitor?: IVisitor;
}
export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription;
export type TGoRoomItem = IGoRoomItem | TSubscriptionModel | ISubscription | IOmnichannelRoomVisitor;
const navigate = ({
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 ({
item,
isMasterDetail = false,

View File

@ -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<TSubscriptionModel[]>([]);
const [channels, setChannels] = useState<ISearchLocal[]>([]);
const getChannels = debounce(async (keyword = '') => {
try {

View File

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

View File

@ -19,7 +19,6 @@ import Button from '../containers/Button';
import SafeAreaView from '../containers/SafeAreaView';
import { MultiSelect } from '../containers/UIKit/MultiSelect';
import { ILivechatVisitor } from '../definitions/ILivechatVisitor';
import { ITagsOmnichannel } from '../definitions/ITagsOmnichannel';
import { IApplicationState, ISubscription } from '../definitions';
import { ChatsStackParamList } from '../stacks/types';
import sharedStyles from './Styles';
@ -48,12 +47,6 @@ interface ITitle {
theme: string;
}
interface IField {
_id: string;
visibility: string;
scope: string;
}
interface IInputs {
livechatData: {
[key: string]: any;
@ -116,17 +109,17 @@ const LivechatEditView = ({
const visitor = route.params?.roomUser ?? {};
const getCustomFields = async () => {
const result: any = await RocketChat.getCustomFields();
const result = await RocketChat.getCustomFields();
if (result.success && result.customFields?.length) {
const visitorCustomFields = result.customFields
.filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'visitor')
.map((field: IField) => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' }))
.reduce((ret: IField, field: IField) => ({ ...field, ...ret }));
.filter(field => field.visibility !== 'hidden' && field.scope === 'visitor')
.map(field => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' }))
.reduce((ret, field) => ({ ...field, ...ret }), {});
const livechatCustomFields = result.customFields
.filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'room')
.map((field: IField) => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' }))
.reduce((ret: IField, field: IField) => ({ ...field, ...ret }));
.filter(field => field.visibility !== 'hidden' && field.scope === 'room')
.map(field => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' }))
.reduce((ret, field) => ({ ...field, ...ret }), {});
return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields });
}
@ -142,7 +135,7 @@ const LivechatEditView = ({
}, [availableUserTags]);
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 availableTags = tags
.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 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<INewMessageViewProps, INewMessageVi
};
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({
search: result
});
@ -313,7 +301,7 @@ class NewMessageView extends React.Component<INewMessageViewProps, INewMessageVi
<FlatList
data={search.length > 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}

View File

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

View File

@ -28,6 +28,7 @@ import { goRoom, TGoRoomItem } from '../../utils/goRoom';
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import log from '../../utils/log';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { RoomTypes } from '../../lib/rocketchat/methods/roomTypeToApiType';
import styles from './styles';
const PAGE_SIZE = 25;
@ -590,7 +591,8 @@ class RoomMembersView extends React.Component<IRoomMembersViewProps, IRoomMember
try {
const { room, members, membersFiltered } = this.state;
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) });
EventEmitter.emit(LISTENER, { message });
this.setState({

View File

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

View File

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

View File

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