From 4224f4d9d0f3d125655b10b5661e164b3cee0a47 Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Fri, 25 Feb 2022 18:24:08 -0300 Subject: [PATCH 1/2] Chore: Migrate to TS RommInfoEditView (#3766) * initial commit * fix last types * fix import * fix lint --- app/containers/UIKit/MultiSelect/index.tsx | 2 +- app/lib/rocketchat/services/restApi.ts | 6 +- app/stacks/MasterDetailStack/types.ts | 2 +- ...SwitchContainer.js => SwitchContainer.tsx} | 36 ++- .../RoomInfoEditView/{index.js => index.tsx} | 272 ++++++++++-------- .../RoomInfoEditView/{styles.js => styles.ts} | 0 6 files changed, 172 insertions(+), 146 deletions(-) rename app/views/RoomInfoEditView/{SwitchContainer.js => SwitchContainer.tsx} (73%) rename app/views/RoomInfoEditView/{index.js => index.tsx} (76%) rename app/views/RoomInfoEditView/{styles.js => styles.ts} (100%) diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx index bedac3257..1853b4028 100644 --- a/app/containers/UIKit/MultiSelect/index.tsx +++ b/app/containers/UIKit/MultiSelect/index.tsx @@ -24,7 +24,7 @@ interface IMultiSelect { multiselect?: boolean; onSearch?: () => void; onClose?: () => void; - inputStyle: object; + inputStyle?: object; value?: any[]; disabled?: boolean | object; theme: string; diff --git a/app/lib/rocketchat/services/restApi.ts b/app/lib/rocketchat/services/restApi.ts index c7353d4a0..b24129af5 100644 --- a/app/lib/rocketchat/services/restApi.ts +++ b/app/lib/rocketchat/services/restApi.ts @@ -1,7 +1,7 @@ -import sdk from './sdk'; +import { SubscriptionType } from '../../../definitions'; import { TEAM_TYPE } from '../../../definitions/ITeam'; import roomTypeToApiType, { RoomTypes } from '../methods/roomTypeToApiType'; -import { SubscriptionType } from '../../../definitions'; +import sdk from './sdk'; export const createChannel = ({ name, @@ -575,7 +575,7 @@ export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: strin // @ts-ignore sdk.get('chat.ignoreUser', { rid, userId, ignore }); -export const toggleArchiveRoom = (roomId: string, t: RoomTypes, archive: boolean): any => { +export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: boolean): any => { if (archive) { // RC 0.48.0 // TODO: missing definitions from server diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index 533b7f381..0db458cd9 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -45,7 +45,7 @@ export type ModalStackParamList = { title: string; infoText: string; nextAction: Function; - showAlert: () => void | boolean; + showAlert?: () => void | boolean; isSearch?: boolean; onSearch?: Function; isRadio?: boolean; diff --git a/app/views/RoomInfoEditView/SwitchContainer.js b/app/views/RoomInfoEditView/SwitchContainer.tsx similarity index 73% rename from app/views/RoomInfoEditView/SwitchContainer.js rename to app/views/RoomInfoEditView/SwitchContainer.tsx index c57f092e4..0ae854e7e 100644 --- a/app/views/RoomInfoEditView/SwitchContainer.js +++ b/app/views/RoomInfoEditView/SwitchContainer.tsx @@ -1,11 +1,24 @@ import React from 'react'; -import { Switch, Text, View } from 'react-native'; -import PropTypes from 'prop-types'; +import { Switch, Text, TextStyle, View, ViewStyle } from 'react-native'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import styles from './styles'; -const SwitchContainer = React.memo( +interface ISwitchContainer { + value: boolean; + disabled?: boolean; + leftLabelPrimary: string; + leftLabelSecondary: string; + rightLabelPrimary?: string; + rightLabelSecondary?: string; + onValueChange: (value: any) => void; + theme: string; + testID: string; + labelContainerStyle?: ViewStyle; + leftLabelStyle?: TextStyle; +} + +const SwitchContainer: React.FC = React.memo( ({ children, value, @@ -21,7 +34,7 @@ const SwitchContainer = React.memo( leftLabelStyle }) => ( <> - + {leftLabelPrimary && ( @@ -57,19 +70,4 @@ const SwitchContainer = React.memo( ) ); -SwitchContainer.propTypes = { - value: PropTypes.bool, - disabled: PropTypes.bool, - leftLabelPrimary: PropTypes.string, - leftLabelSecondary: PropTypes.string, - rightLabelPrimary: PropTypes.string, - rightLabelSecondary: PropTypes.string, - onValueChange: PropTypes.func, - theme: PropTypes.string, - testID: PropTypes.string, - labelContainerStyle: PropTypes.object, - leftLabelStyle: PropTypes.object, - children: PropTypes.any -}; - export default SwitchContainer; diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.tsx similarity index 76% rename from app/views/RoomInfoEditView/index.js rename to app/views/RoomInfoEditView/index.tsx index 251629187..4c77b9015 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.tsx @@ -1,75 +1,98 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Alert, Keyboard, ScrollView, Text, TouchableOpacity, View } from 'react-native'; -import { connect } from 'react-redux'; +import { Q } from '@nozbe/watermelondb'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; -import ImagePicker from 'react-native-image-crop-picker'; import { dequal } from 'dequal'; import isEmpty from 'lodash/isEmpty'; -import { Q } from '@nozbe/watermelondb'; +import React from 'react'; +import { Alert, Keyboard, ScrollView, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import ImagePicker, { Image } from 'react-native-image-crop-picker'; +import { connect } from 'react-redux'; -import { compareServerVersion } from '../../lib/utils'; -import database from '../../lib/database'; -import { deleteRoom as deleteRoomAction } from '../../actions/room'; -import KeyboardView from '../../presentation/KeyboardView'; -import sharedStyles from '../Styles'; -import scrollPersistTaps from '../../utils/scrollPersistTaps'; -import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; -import { LISTENER } from '../../containers/Toast'; -import EventEmitter from '../../utils/events'; -import RocketChat from '../../lib/rocketchat'; -import RCTextInput from '../../containers/TextInput'; -import Loading from '../../containers/Loading'; -import random from '../../utils/random'; -import log, { events, logEvent } from '../../utils/log'; -import I18n from '../../i18n'; -import StatusBar from '../../containers/StatusBar'; +import { deleteRoom } from '../../actions/room'; import { themes } from '../../constants/colors'; -import { withTheme } from '../../theme'; -import { MultiSelect } from '../../containers/UIKit/MultiSelect'; -import { MessageTypeValues } from '../../utils/messageTypes'; -import SafeAreaView from '../../containers/SafeAreaView'; import Avatar from '../../containers/Avatar'; +import Loading from '../../containers/Loading'; +import SafeAreaView from '../../containers/SafeAreaView'; +import StatusBar from '../../containers/StatusBar'; +import RCTextInput from '../../containers/TextInput'; +import { LISTENER } from '../../containers/Toast'; +import { MultiSelect } from '../../containers/UIKit/MultiSelect'; +import { IApplicationState, IBaseScreen, ISubscription, TSubscriptionModel } from '../../definitions'; +import { ERoomType } from '../../definitions/ERoomType'; +import I18n from '../../i18n'; +import database from '../../lib/database'; import { CustomIcon } from '../../lib/Icons'; -import SwitchContainer from './SwitchContainer'; +import RocketChat from '../../lib/rocketchat'; +import { compareServerVersion } from '../../lib/utils'; +import KeyboardView from '../../presentation/KeyboardView'; +import { TSupportedPermissions } from '../../reducers/permissions'; +import { ModalStackParamList } from '../../stacks/MasterDetailStack/types'; +import { ChatsStackParamList } from '../../stacks/types'; +import { withTheme } from '../../theme'; +import EventEmitter from '../../utils/events'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; +import log, { events, logEvent } from '../../utils/log'; +import { MessageTypeValues } from '../../utils/messageTypes'; +import random from '../../utils/random'; +import scrollPersistTaps from '../../utils/scrollPersistTaps'; +import { IAvatar } from '../ProfileView/interfaces'; +import sharedStyles from '../Styles'; import styles from './styles'; +import SwitchContainer from './SwitchContainer'; -const PERMISSION_SET_READONLY = 'set-readonly'; -const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly'; -const PERMISSION_ARCHIVE = 'archive-room'; -const PERMISSION_UNARCHIVE = 'unarchive-room'; -const PERMISSION_DELETE_C = 'delete-c'; -const PERMISSION_DELETE_P = 'delete-p'; -const PERMISSION_DELETE_TEAM = 'delete-team'; +interface IRoomInfoEditViewState { + room: ISubscription; + avatar: IAvatar; + permissions: Record; + name: string; + description?: string; + topic?: string; + announcement?: string; + joinCode: string; + nameError: any; + saving: boolean; + t: boolean; + ro: boolean; + reactWhenReadOnly?: boolean; + archived: boolean; + systemMessages?: boolean | string[]; + enableSysMes?: boolean | string[]; + encrypted?: boolean; +} + +interface IRoomInfoEditViewProps extends IBaseScreen { + serverVersion?: string; + encryptionEnabled: boolean; + theme: string; + setReadOnlyPermission: string[]; + setReactWhenReadOnlyPermission: string[]; + archiveRoomPermission: string[]; + unarchiveRoomPermission: string[]; + deleteCPermission: string[]; + deletePPermission: string[]; + deleteTeamPermission: string[]; + isMasterDetail: boolean; +} + +class RoomInfoEditView extends React.Component { + randomValue = random(15); + private querySubscription: any; // Observable dont have unsubscribe prop + private room!: TSubscriptionModel; + private name!: TextInput | null; + private description!: TextInput | null; + private topic!: TextInput | null; + private announcement!: TextInput | null; + private joinCode!: TextInput | null; -class RoomInfoEditView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Room_Info_Edit') }); - static propTypes = { - navigation: PropTypes.object, - route: PropTypes.object, - deleteRoom: PropTypes.func, - serverVersion: PropTypes.string, - encryptionEnabled: PropTypes.bool, - theme: PropTypes.string, - setReadOnlyPermission: PropTypes.array, - setReactWhenReadOnlyPermission: PropTypes.array, - archiveRoomPermission: PropTypes.array, - unarchiveRoomPermission: PropTypes.array, - deleteCPermission: PropTypes.array, - deletePPermission: PropTypes.array, - deleteTeamPermission: PropTypes.array, - isMasterDetail: PropTypes.bool - }; - - constructor(props) { + constructor(props: IRoomInfoEditViewProps) { super(props); this.state = { - room: {}, - avatar: {}, - permissions: {}, + room: {} as ISubscription, + avatar: {} as IAvatar, + permissions: {} as Record, name: '', description: '', topic: '', @@ -134,14 +157,15 @@ class RoomInfoEditView extends React.Component { ); this.setState({ + // @ts-ignore - Solved by migrating the hasPermission function permissions: { - [PERMISSION_SET_READONLY]: result[0], - [PERMISSION_SET_REACT_WHEN_READONLY]: result[1], - [PERMISSION_ARCHIVE]: result[2], - [PERMISSION_UNARCHIVE]: result[3], - [PERMISSION_DELETE_C]: result[4], - [PERMISSION_DELETE_P]: result[5], - ...(this.room.teamMain && { [PERMISSION_DELETE_TEAM]: result[6] }) + 'set-readonly': result[0], + 'set-react-when-readonly': result[1], + 'archive-room': result[2], + 'unarchive-room': result[3], + 'delete-c': result[4], + 'delete-p': result[5], + ...(this.room.teamMain && { 'delete-team': result[6] }) } }); } catch (e) { @@ -149,10 +173,10 @@ class RoomInfoEditView extends React.Component { } }; - init = room => { - const { description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes, encrypted } = room; + init = (room: ISubscription) => { + const { description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, encrypted } = room; + const sysMes = room.sysMes as string[]; // fake password just to user knows about it - this.randomValue = random(15); this.setState({ room, name: RocketChat.getRoomTitle(room), @@ -160,7 +184,7 @@ class RoomInfoEditView extends React.Component { topic, announcement, t: t === 'p', - avatar: {}, + avatar: {} as IAvatar, ro, reactWhenReadOnly, joinCode: joinCodeRequired ? this.randomValue : '', @@ -200,6 +224,7 @@ class RoomInfoEditView extends React.Component { avatar } = this.state; const { joinCodeRequired } = room; + const sysMes = room.sysMes as string[]; return !( room.name === name && room.description === description && @@ -209,8 +234,8 @@ class RoomInfoEditView extends React.Component { (room.t === 'p') === t && room.ro === ro && room.reactWhenReadOnly === reactWhenReadOnly && - dequal(room.sysMes, systemMessages) && - enableSysMes === (room.sysMes && room.sysMes.length > 0) && + dequal(sysMes, systemMessages) && + enableSysMes === (sysMes && sysMes.length > 0) && room.encrypted === encrypted && isEmpty(avatar) ); @@ -245,7 +270,7 @@ class RoomInfoEditView extends React.Component { // Clear error objects await this.clearErrors(); - const params = {}; + const params = {} as any; // Name if (room.name !== name) { @@ -268,7 +293,8 @@ class RoomInfoEditView extends React.Component { params.roomAnnouncement = announcement; } // Room Type - if (room.t !== t) { + // This logic is strange to me, since in the code t is boolean, but room.t is string + if (!!room.t !== t) { params.roomType = t ? 'p' : 'c'; } // Read Only @@ -296,7 +322,7 @@ class RoomInfoEditView extends React.Component { try { await RocketChat.saveRoomSettings(room.rid, params); - } catch (e) { + } catch (e: any) { if (e.error === 'error-invalid-room-name') { this.setState({ nameError: e }); } @@ -317,20 +343,26 @@ class RoomInfoEditView extends React.Component { deleteTeam = async () => { const { room } = this.state; - const { navigation, deleteCPermission, deletePPermission, deleteRoom } = this.props; + const { navigation, deleteCPermission, deletePPermission, dispatch } = this.props; try { const db = database.active; const subCollection = db.get('subscriptions'); - const teamChannels = await subCollection.query(Q.where('team_id', room.teamId), Q.where('team_main', Q.notEq(true))); + const teamChannels = await subCollection.query( + Q.where('team_id', room.teamId as string), + Q.where('team_main', Q.notEq(true)) + ); const teamChannelOwner = []; + // @ts-ignore - wm schema type error dont including array for (let i = 0; i < teamChannels.length; i += 1) { + // @ts-ignore - wm schema type error dont including array const permissionType = teamChannels[i].t === 'c' ? deleteCPermission : deletePPermission; + // @ts-ignore - wm schema type error dont including array // eslint-disable-next-line no-await-in-loop const permissions = await RocketChat.hasPermission([permissionType], teamChannels[i].rid); - if (permissions[0]) { + // @ts-ignore - wm schema type error dont including array teamChannelOwner.push(teamChannels[i]); } } @@ -340,11 +372,11 @@ class RoomInfoEditView extends React.Component { title: 'Delete_Team', data: teamChannelOwner, infoText: 'Select_channels_to_delete', - nextAction: selected => { + nextAction: (selected: Record) => { showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), - onPress: () => deleteRoom('team', room, selected) + onPress: () => deleteRoom(ERoomType.t, room, selected) }); } }); @@ -352,10 +384,10 @@ class RoomInfoEditView extends React.Component { showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), - onPress: () => deleteRoom('team', room) + onPress: () => dispatch(deleteRoom(ERoomType.t, room)) }); } - } catch (e) { + } catch (e: any) { log(e); showErrorAlert( e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }), @@ -366,7 +398,7 @@ class RoomInfoEditView extends React.Component { delete = () => { const { room } = this.state; - const { deleteRoom } = this.props; + const { dispatch } = this.props; Alert.alert( I18n.t('Are_you_sure_question_mark'), @@ -379,7 +411,7 @@ class RoomInfoEditView extends React.Component { { text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), style: 'destructive', - onPress: () => deleteRoom('channel', room) + onPress: () => dispatch(deleteRoom(ERoomType.c, room)) } ], { cancelable: false } @@ -421,14 +453,14 @@ class RoomInfoEditView extends React.Component { const { room, permissions } = this.state; if (room.teamMain) { - return permissions[PERMISSION_DELETE_TEAM]; + return permissions['delete-team']; } if (room.t === 'p') { - return permissions[PERMISSION_DELETE_P]; + return permissions['delete-p']; } - return permissions[PERMISSION_DELETE_C]; + return permissions['delete-c']; }; renderSystemMessages = () => { @@ -445,9 +477,9 @@ class RoomInfoEditView extends React.Component { value: m.value, text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } }))} - onChange={({ value }) => this.setState({ systemMessages: value })} + onChange={({ value }: { value: boolean }) => this.setState({ systemMessages: value })} placeholder={{ text: I18n.t('Hide_System_Messages') }} - value={systemMessages} + value={systemMessages as string[]} context={BLOCK_CONTEXT.FORM} multiselect theme={theme} @@ -466,7 +498,7 @@ class RoomInfoEditView extends React.Component { }; try { - const response = await ImagePicker.openPicker(options); + const response: Image = await ImagePicker.openPicker(options); this.setState({ avatar: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' } }); } catch (e) { console.log(e); @@ -477,27 +509,27 @@ class RoomInfoEditView extends React.Component { this.setState({ avatar: { data: null } }); }; - toggleRoomType = value => { + toggleRoomType = (value: boolean) => { logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE); this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted })); }; - toggleReadOnly = value => { + toggleReadOnly = (value: boolean) => { logEvent(events.RI_EDIT_TOGGLE_READ_ONLY); this.setState({ ro: value }); }; - toggleReactions = value => { + toggleReactions = (value: boolean) => { logEvent(events.RI_EDIT_TOGGLE_REACTIONS); this.setState({ reactWhenReadOnly: value }); }; - toggleHideSystemMessages = value => { + toggleHideSystemMessages = (value: boolean) => { logEvent(events.RI_EDIT_TOGGLE_SYSTEM_MSG); this.setState(({ systemMessages }) => ({ enableSysMes: value, systemMessages: value ? systemMessages : [] })); }; - toggleEncrypted = value => { + toggleEncrypted = (value: boolean) => { logEvent(events.RI_EDIT_TOGGLE_ENCRYPTED); this.setState({ encrypted: value }); }; @@ -538,15 +570,15 @@ class RoomInfoEditView extends React.Component { + disabled={compareServerVersion(serverVersion || '', 'lowerThan', '3.6.0')}> - {compareServerVersion(serverVersion, 'lowerThan', '3.6.0') ? null : ( + {serverVersion && compareServerVersion(serverVersion, 'lowerThan', '3.6.0') ? undefined : ( @@ -563,7 +595,7 @@ class RoomInfoEditView extends React.Component { value={name} onChangeText={value => this.setState({ name: value })} onSubmitEditing={() => { - this.description.focus(); + this.description?.focus(); }} error={nameError} theme={theme} @@ -577,7 +609,7 @@ class RoomInfoEditView extends React.Component { value={description} onChangeText={value => this.setState({ description: value })} onSubmitEditing={() => { - this.topic.focus(); + this.topic?.focus(); }} theme={theme} testID='room-info-edit-view-description' @@ -590,7 +622,7 @@ class RoomInfoEditView extends React.Component { value={topic} onChangeText={value => this.setState({ topic: value })} onSubmitEditing={() => { - this.announcement.focus(); + this.announcement?.focus(); }} theme={theme} testID='room-info-edit-view-topic' @@ -603,7 +635,7 @@ class RoomInfoEditView extends React.Component { value={announcement} onChangeText={value => this.setState({ announcement: value })} onSubmitEditing={() => { - this.joinCode.focus(); + this.joinCode?.focus(); }} theme={theme} testID='room-info-edit-view-announcement' @@ -647,19 +679,19 @@ class RoomInfoEditView extends React.Component { rightLabelPrimary={I18n.t('Read_Only')} rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')} onValueChange={this.toggleReadOnly} - disabled={!permissions[PERMISSION_SET_READONLY] || room.broadcast} + disabled={!permissions['set-readonly'] || room.broadcast} theme={theme} testID='room-info-edit-view-ro' /> {ro && !room.broadcast ? ( @@ -670,9 +702,9 @@ class RoomInfoEditView extends React.Component { ] : null} - {!compareServerVersion(serverVersion, 'lowerThan', '3.0.0') ? ( + {serverVersion && !compareServerVersion(serverVersion, 'lowerThan', '3.0.0') ? ( {archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE')} @@ -771,21 +803,17 @@ class RoomInfoEditView extends React.Component { } } -const mapStateToProps = state => ({ - serverVersion: state.server.version, +const mapStateToProps = (state: IApplicationState) => ({ + serverVersion: state.server.version as string, encryptionEnabled: state.encryption.enabled, - setReadOnlyPermission: state.permissions[PERMISSION_SET_READONLY], - setReactWhenReadOnlyPermission: state.permissions[PERMISSION_SET_REACT_WHEN_READONLY], - archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE], - unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE], - deleteCPermission: state.permissions[PERMISSION_DELETE_C], - deletePPermission: state.permissions[PERMISSION_DELETE_P], - deleteTeamPermission: state.permissions[PERMISSION_DELETE_TEAM], + setReadOnlyPermission: state.permissions['set-readonly'] as string[], + setReactWhenReadOnlyPermission: state.permissions['set-react-when-readonly'] as string[], + archiveRoomPermission: state.permissions['archive-room'] as string[], + unarchiveRoomPermission: state.permissions['unarchive-room'] as string[], + deleteCPermission: state.permissions['delete-c'] as string[], + deletePPermission: state.permissions['delete-p'] as string[], + deleteTeamPermission: state.permissions['delete-team'] as string[], isMasterDetail: state.app.isMasterDetail }); -const mapDispatchToProps = dispatch => ({ - deleteRoom: (roomType, room, selected) => dispatch(deleteRoomAction(roomType, room, selected)) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(withTheme(RoomInfoEditView)); +export default connect(mapStateToProps)(withTheme(RoomInfoEditView)); diff --git a/app/views/RoomInfoEditView/styles.js b/app/views/RoomInfoEditView/styles.ts similarity index 100% rename from app/views/RoomInfoEditView/styles.js rename to app/views/RoomInfoEditView/styles.ts From 4172d563b7eee099ff51e0446dd792cf02471f9a Mon Sep 17 00:00:00 2001 From: Gleidson Daniel Silva Date: Fri, 25 Feb 2022 18:32:03 -0300 Subject: [PATCH 2/2] Chore: Server API types POC - loadMessagesForRoom (#3765) * create interface and implements base types * fix some types * Update app/lib/methods/updateMessages.ts Co-authored-by: Diego Mello * fix date type * apply types changes * fix type * fix date value * fix types * typescript things... Co-authored-by: Diego Mello --- app/definitions/IMessage.ts | 62 +++++++++++++++---- app/definitions/IThread.ts | 1 + app/definitions/IUrl.ts | 16 ++--- app/definitions/rest/v1/channels.ts | 15 ++++- app/definitions/rest/v1/groups.ts | 9 ++- app/definitions/rest/v1/im.ts | 9 ++- app/definitions/rest/v1/omnichannel.ts | 2 +- app/lib/encryption/encryption.ts | 9 +-- app/lib/methods/getThreadName.ts | 4 +- app/lib/methods/helpers/buildMessage.ts | 4 +- app/lib/methods/loadMessagesForRoom.ts | 28 ++++----- app/lib/methods/updateMessages.ts | 10 +-- .../rocketchat/methods/roomTypeToApiType.ts | 16 +++-- app/views/ThreadMessagesView/index.tsx | 2 +- 14 files changed, 126 insertions(+), 61 deletions(-) diff --git a/app/definitions/IMessage.ts b/app/definitions/IMessage.ts index bc8f793da..fcbf90d5f 100644 --- a/app/definitions/IMessage.ts +++ b/app/definitions/IMessage.ts @@ -3,7 +3,7 @@ import { MarkdownAST } from '@rocket.chat/message-parser'; import { IAttachment } from './IAttachment'; import { IReaction } from './IReaction'; -import { IUrl } from './IUrl'; +import { IUrlFromServer } from './IUrl'; export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj'; @@ -59,22 +59,61 @@ export interface ILastMessage { status: boolean; } -export interface IMessage { +interface IMessageFile { + _id: string; + name: 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 { _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[]; +} + +export interface ILoadMoreMessage { + _id: string; + rid: string; + ts: string; + t: string; + msg: string; +} + +export interface IMessage extends IMessageFromServer { id: string; t?: MessageType; - ts: string | Date; - u: IUserMessage; alias?: string; parseUrls?: boolean; - groupable?: boolean; avatar?: string; emoji?: string; - attachments?: IAttachment[]; - urls?: IUrl[]; - _updatedAt: string | Date; status?: number; pinned?: boolean; starred?: boolean; @@ -88,8 +127,6 @@ export interface IMessage { tcount?: number; tlm?: string | Date; replies?: string[]; - mentions?: IUserMention[]; - channels?: IUserChannel[]; unread?: boolean; autoTranslate?: boolean; translations?: ITranslations[]; @@ -97,8 +134,9 @@ export interface IMessage { blocks?: any; e2e?: string; tshow?: boolean; - md?: MarkdownAST; subscription?: { id: string }; } export type TMessageModel = IMessage & Model; + +export type TTypeMessages = IMessageFromServer | ILoadMoreMessage | IMessage; diff --git a/app/definitions/IThread.ts b/app/definitions/IThread.ts index 824b2544e..1294981c6 100644 --- a/app/definitions/IThread.ts +++ b/app/definitions/IThread.ts @@ -30,6 +30,7 @@ export interface IThreadResult { channels?: IUserChannel[]; replies?: string[]; tcount?: number; + status?: string; tlm?: string | Date; } diff --git a/app/definitions/IUrl.ts b/app/definitions/IUrl.ts index 3e613bab3..3210afa23 100644 --- a/app/definitions/IUrl.ts +++ b/app/definitions/IUrl.ts @@ -1,11 +1,3 @@ -export interface IUrl { - _id: number; - title: string; - description: string; - image: string; - url: string; -} - export interface IUrlFromServer { url: string; meta: { @@ -47,3 +39,11 @@ export interface IUrlFromServer { }; ignoreParse: boolean; } + +export interface IUrl extends IUrlFromServer { + _id: number; + title: string; + description: string; + image: string; + url: string; +} diff --git a/app/definitions/rest/v1/channels.ts b/app/definitions/rest/v1/channels.ts index d16798774..e507d970e 100644 --- a/app/definitions/rest/v1/channels.ts +++ b/app/definitions/rest/v1/channels.ts @@ -1,10 +1,16 @@ -import type { IMessage } from '../../IMessage'; +import type { IMessage, IMessageFromServer } from '../../IMessage'; import type { IRoom } from '../../IRoom'; import type { IUser } from '../../IUser'; export type ChannelsEndpoints = { 'channels.files': { - GET: (params: { roomId: IRoom['_id']; offset: number; count: number; sort: string; query: string }) => { + GET: (params: { + roomId: IRoom['_id']; + offset: number; + count: number; + sort: string | { uploadedAt: number }; + query: string; + }) => { files: IMessage[]; total: number; }; @@ -17,4 +23,9 @@ export type ChannelsEndpoints = { total: number; }; }; + 'channels.history': { + GET: (params: { roomId: string; count: number; latest?: string }) => { + messages: IMessageFromServer[]; + }; + }; }; diff --git a/app/definitions/rest/v1/groups.ts b/app/definitions/rest/v1/groups.ts index ca6454734..8518fcd05 100644 --- a/app/definitions/rest/v1/groups.ts +++ b/app/definitions/rest/v1/groups.ts @@ -1,10 +1,10 @@ -import type { IMessage } from '../../IMessage'; +import type { IMessage, IMessageFromServer } from '../../IMessage'; import type { IRoom } from '../../IRoom'; import type { IUser } from '../../IUser'; export type GroupsEndpoints = { 'groups.files': { - GET: (params: { roomId: IRoom['_id']; count: number; sort: string; query: string }) => { + GET: (params: { roomId: IRoom['_id']; count: number; sort: string | { uploadedAt: number }; query: string }) => { files: IMessage[]; total: number; }; @@ -17,4 +17,9 @@ export type GroupsEndpoints = { total: number; }; }; + 'groups.history': { + GET: (params: { roomId: string; count: number; latest?: string }) => { + messages: IMessageFromServer[]; + }; + }; }; diff --git a/app/definitions/rest/v1/im.ts b/app/definitions/rest/v1/im.ts index a9502ab2d..b2927b03c 100644 --- a/app/definitions/rest/v1/im.ts +++ b/app/definitions/rest/v1/im.ts @@ -1,4 +1,4 @@ -import type { IMessage } from '../../IMessage'; +import type { IMessage, IMessageFromServer } from '../../IMessage'; import type { IRoom } from '../../IRoom'; import type { IUser } from '../../IUser'; @@ -20,7 +20,7 @@ export type ImEndpoints = { }; }; 'im.files': { - GET: (params: { roomId: IRoom['_id']; count: number; sort: string; query: string }) => { + GET: (params: { roomId: IRoom['_id']; count: number; sort: string | { uploadedAt: number }; query: string }) => { files: IMessage[]; total: number; }; @@ -33,4 +33,9 @@ export type ImEndpoints = { total: number; }; }; + 'im.history': { + GET: (params: { roomId: string; count: number; latest?: string }) => { + messages: IMessageFromServer[]; + }; + }; }; diff --git a/app/definitions/rest/v1/omnichannel.ts b/app/definitions/rest/v1/omnichannel.ts index 784415a53..df4441370 100644 --- a/app/definitions/rest/v1/omnichannel.ts +++ b/app/definitions/rest/v1/omnichannel.ts @@ -179,7 +179,7 @@ export type OmnichannelEndpoints = { departmentId?: ILivechatAgent['_id']; offset: number; count: number; - sort: string; + sort: string | { uploadedAt: number }; }) => { queue: { chats: number; diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index be979e278..d5d87d96f 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -244,7 +244,7 @@ class Encryption { const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch(); // Concat messages/threads/threadMessages - let toDecrypt: (TThreadModel | TThreadMessageModel)[] = [ + let toDecrypt: (TThreadModel | TThreadMessageModel | TMessageModel)[] = [ ...messagesToDecrypt, ...threadsToDecrypt, ...threadMessagesToDecrypt @@ -259,7 +259,7 @@ class Encryption { newMessage = await this.decryptMessage({ t, rid, - msg, + msg: msg as string, tmsg }); } @@ -464,12 +464,13 @@ class Encryption { } const { rid } = message; - const roomE2E = await this.getRoomInstance(rid as string); + const roomE2E = await this.getRoomInstance(rid); return roomE2E.decrypt(message); }; // Decrypt multiple messages - decryptMessages = (messages: IMessage[]) => Promise.all(messages.map((m: IMessage) => this.decryptMessage(m))); + decryptMessages = (messages: Partial[]) => + Promise.all(messages.map((m: Partial) => this.decryptMessage(m as IMessage))); // Decrypt multiple subscriptions decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s))); diff --git a/app/lib/methods/getThreadName.ts b/app/lib/methods/getThreadName.ts index b6ed94580..3f4c42882 100644 --- a/app/lib/methods/getThreadName.ts +++ b/app/lib/methods/getThreadName.ts @@ -6,9 +6,9 @@ import { getThreadById } from '../database/services/Thread'; import log from '../../utils/log'; import { Encryption } from '../encryption'; import getSingleMessage from './getSingleMessage'; -import { IThread, TThreadModel } from '../../definitions'; +import { IMessage, IThread, TThreadModel } from '../../definitions'; -const buildThreadName = (thread: IThread): string | undefined => thread.msg || thread?.attachments?.[0]?.title; +const buildThreadName = (thread: IThread | IMessage): string | undefined => thread.msg || thread?.attachments?.[0]?.title; const getThreadName = async (rid: string, tmid: string, messageId: string): Promise => { let tmsg: string | undefined; diff --git a/app/lib/methods/helpers/buildMessage.ts b/app/lib/methods/helpers/buildMessage.ts index 44c30ffa2..b17574c97 100644 --- a/app/lib/methods/helpers/buildMessage.ts +++ b/app/lib/methods/helpers/buildMessage.ts @@ -1,8 +1,8 @@ -import { IMessage } from '../../../definitions'; +import { IMessage, IThreadResult } from '../../../definitions'; import messagesStatus from '../../../constants/messagesStatus'; import normalizeMessage from './normalizeMessage'; -export default (message: IMessage): IMessage => { +export default (message: Partial | IThreadResult): Partial | IThreadResult => { message.status = messagesStatus.SENT; return normalizeMessage(message); }; diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index db64a05ca..6eef4fa0c 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -1,13 +1,12 @@ import moment from 'moment'; -import { MESSAGE_TYPE_LOAD_MORE } from '../../constants/messageTypeLoad'; +import { IMessage, MessageType, TMessageModel } from '../../definitions'; import log from '../../utils/log'; import { getMessageById } from '../database/services/Message'; +import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType'; +import sdk from '../rocketchat/services/sdk'; import { generateLoadMoreId } from '../utils'; import updateMessages from './updateMessages'; -import { IMessage, TMessageModel } from '../../definitions'; -import sdk from '../rocketchat/services/sdk'; -import roomTypeToApiType, { RoomTypes } from '../rocketchat/methods/roomTypeToApiType'; const COUNT = 50; @@ -23,9 +22,8 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: string; } // RC 0.48.0 - // @ts-ignore - const data: any = await sdk.get(`${apiType}.history`, params); - if (!data || data.status === 'error') { + const data = await sdk.get(`${apiType}.history`, params); + if (!data.success) { return []; } return data.messages; @@ -36,22 +34,22 @@ export default function loadMessagesForRoom(args: { t: RoomTypes; latest: string; loaderItem: TMessageModel; -}): Promise { +}): Promise[]> { return new Promise(async (resolve, reject) => { try { - const data = await load(args); + const data: Partial[] = await load(args); if (data?.length) { const lastMessage = data[data.length - 1]; - const lastMessageRecord = await getMessageById(lastMessage._id); + const lastMessageRecord = await getMessageById(lastMessage._id as string); if (!lastMessageRecord && data.length === COUNT) { - const loadMoreItem = { - _id: generateLoadMoreId(lastMessage._id), + const loadMoreMessage = { + _id: generateLoadMoreId(lastMessage._id as string), rid: lastMessage.rid, - ts: moment(lastMessage.ts).subtract(1, 'millisecond'), - t: MESSAGE_TYPE_LOAD_MORE, + ts: moment(lastMessage.ts).subtract(1, 'millisecond').toString(), + t: 'load_more' as MessageType, msg: lastMessage.msg }; - data.push(loadMoreItem); + data.push(loadMoreMessage); } await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem }); return resolve(data); diff --git a/app/lib/methods/updateMessages.ts b/app/lib/methods/updateMessages.ts index 1e86d7997..6d4115ae9 100644 --- a/app/lib/methods/updateMessages.ts +++ b/app/lib/methods/updateMessages.ts @@ -12,8 +12,8 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; interface IUpdateMessages { rid: string; - update: IMessage[]; - remove?: IMessage[]; + update: Partial[]; + remove?: Partial[]; loaderItem?: TMessageModel; } @@ -37,7 +37,7 @@ export default async function updateMessages({ // Decrypt these messages update = await Encryption.decryptMessages(update); - const messagesIds: string[] = [...update.map(m => m._id), ...remove.map(m => m._id)]; + const messagesIds: string[] = [...update.map(m => m._id as string), ...remove.map(m => m._id as string)]; const msgCollection = db.get('messages'); const threadCollection = db.get('threads'); const threadMessagesCollection = db.get('thread_messages'); @@ -49,11 +49,11 @@ export default async function updateMessages({ .query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds))) .fetch(); - update = update.map(m => buildMessage(m)); + update = update.map(m => buildMessage(m)) as IMessage[]; // filter loaders to delete let loadersToDelete: TMessageModel[] = allMessagesRecords.filter(i1 => - update.find(i2 => i1.id === generateLoadMoreId(i2._id)) + update.find(i2 => i1.id === generateLoadMoreId(i2._id as string)) ); // Delete diff --git a/app/lib/rocketchat/methods/roomTypeToApiType.ts b/app/lib/rocketchat/methods/roomTypeToApiType.ts index ecc0d16c2..b66b78d2c 100644 --- a/app/lib/rocketchat/methods/roomTypeToApiType.ts +++ b/app/lib/rocketchat/methods/roomTypeToApiType.ts @@ -1,8 +1,14 @@ -const types = { - c: 'channels', - d: 'im', - p: 'groups', - l: 'channels' +enum ETypes { + Channels = 'channels', + Im = 'im', + Groups = 'groups' +} + +export const types = { + c: ETypes.Channels, + d: ETypes.Im, + p: ETypes.Groups, + l: ETypes.Channels }; // TODO: refactor this diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 5ceec17af..d2ea4527d 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -287,7 +287,7 @@ class ThreadMessagesView extends React.Component buildMessage(m)); + update = update.map(m => buildMessage(m)) as IThreadResult[]; // filter threads threadsToCreate = update.filter(i1 => !allThreadsRecords.find((i2: { id: string }) => i1._id === i2.id)); threadsToUpdate = allThreadsRecords.filter((i1: { id: string }) => update.find(i2 => i1.id === i2._id));