[FIX] App not prompting join code for password protected channels (#2514)

* Adding joinCode parameter

Co-authored-by: Vitor Leal <vitor_leal2201@hotmail.com>
Co-authored-by: Fernando Aguilar <fernando.aguilar@hotmail.com.br>

* Insert join code input

Signed-off-by: Vitor.Leal <vitor_leal2201@hotmail.com>

* Add joinCode field on db

Signed-off-by: Vitor.Leal <vitor_leal2201@hotmail.com>

* Add label i18 pt-br and en-us

Signed-off-by: Vitor.Leal <vitor_leal2201@hotmail.com>

* Add insert join code text

Signed-off-by: Vitor.Leal <vitor_leal2201@hotmail.com>

* Fix atribute name

Signed-off-by: Vitor.Leal <vitor_leal2201@hotmail.com>

* Add join text

Signed-off-by: Vitor.Leal <vitor_leal2201@hotmail.com>

Co-authored-by: Daniel Maike <danmke@hotmail.com>
Co-authored-by: Fernando Aguilar <fernando.aguilar@hotmail.com.br>

* Fix attributes joinCode, joinCodeRequired and pass attribute param in navigation

Signed-off-by: Daniel Maike <danmke@hotmail.com>

Co-authored-by: Vitor Leal <vitor_leal2201@hotmail.com>

* Fixing attribute joinCodeRequired pass to goRoom

Signed-off-by: Daniel Maike <danmke@hotmail.com>

* Changed textinput style

Signed-off-by: Daniel Maike <danmke@hotmail.com>

Co-authored-by: Vitor Leal <vitor_leal2201@hotmail.com>

* Delete not necessary attribute

Signed-off-by: Daniel Maike <danmke@hotmail.com>

* Fixing input style

Co-authored-by: Vitor Leal <vitor_leal2201@hotmail.com>

* Undo unncessary changes

* use a join code modal

* tests: e2e tests to join protected channel

* fix: undo unnecessary change

* tests: cancel join code

* Remove some tests

* Minor fixes

Co-authored-by: Vitor Leal <vitor_leal2201@hotmail.com>
Co-authored-by: Fernando Aguilar <fernando.aguilar@hotmail.com.br>
Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
Co-authored-by: youssef-md <emaildeyoussefmuhamad@gmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Daniel Maike 2020-12-01 14:30:39 -03:00 committed by GitHub
parent 06521d17cc
commit 13985cf724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 270 additions and 11 deletions

View File

@ -281,6 +281,8 @@ export default {
Invite_Link: 'Invite Link', Invite_Link: 'Invite Link',
Invite_users: 'Invite users', Invite_users: 'Invite users',
Join: 'Join', Join: 'Join',
Join_Code: 'Join Code',
Insert_Join_Code: 'Insert Join Code',
Join_our_open_workspace: 'Join our open workspace', Join_our_open_workspace: 'Join our open workspace',
Join_your_workspace: 'Join your workspace', Join_your_workspace: 'Join your workspace',
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel', Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',

View File

@ -270,6 +270,8 @@ export default {
Invite_Link: 'Link de Convite', Invite_Link: 'Link de Convite',
Invite_users: 'Convidar usuários', Invite_users: 'Convidar usuários',
Join: 'Entrar', Join: 'Entrar',
Join_Code: 'Insira o Código da Sala',
Insert_Join_Code: 'Insira o código para entrar na sala',
Join_our_open_workspace: 'Entra na nossa workspace pública', Join_our_open_workspace: 'Entra na nossa workspace pública',
Join_your_workspace: 'Entre na sua workspace', Join_your_workspace: 'Entre na sua workspace',
Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal', Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal',

View File

@ -705,13 +705,13 @@ const RocketChat = {
}); });
}, },
joinRoom(roomId, type) { joinRoom(roomId, joinCode, type) {
// TODO: join code // TODO: join code
// RC 0.48.0 // RC 0.48.0
if (type === 'p') { if (type === 'p') {
return this.methodCallWrapper('joinRoom', roomId); return this.methodCallWrapper('joinRoom', roomId);
} }
return this.post('channels.join', { roomId }); return this.post('channels.join', { roomId, joinCode });
}, },
triggerBlockAction, triggerBlockAction,
triggerSubmitView, triggerSubmitView,

View File

@ -149,8 +149,9 @@ class DirectoryView extends React.Component {
this.goRoom({ rid: result.room._id, name: item.username, t: 'd' }); this.goRoom({ rid: result.room._id, name: item.username, t: 'd' });
} }
} else { } else {
const { room } = await RocketChat.getRoomInfo(item._id);
this.goRoom({ this.goRoom({
rid: item._id, name: item.name, t: 'c', search: true rid: item._id, name: item.name, joinCodeRequired: room.joinCodeRequired, t: 'c', search: true
}); });
} }
} }

View File

@ -0,0 +1,140 @@
import React, {
useState,
forwardRef,
useImperativeHandle
} from 'react';
import PropTypes from 'prop-types';
import {
View,
Text,
StyleSheet,
InteractionManager
} from 'react-native';
import Modal from 'react-native-modal';
import { connect } from 'react-redux';
import I18n from '../../i18n';
import Button from '../../containers/Button';
import TextInput from '../../containers/TextInput';
import RocketChat from '../../lib/rocketchat';
import sharedStyles from '../Styles';
import { themes } from '../../constants/colors';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
content: {
padding: 16,
width: '100%',
borderRadius: 4
},
title: {
fontSize: 16,
paddingBottom: 8,
...sharedStyles.textBold,
...sharedStyles.textAlignCenter
},
button: {
minWidth: 96,
marginBottom: 0
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between'
},
tablet: {
height: undefined
}
});
const JoinCode = React.memo(forwardRef(({
rid,
t,
onJoin,
isMasterDetail,
theme
}, ref) => {
const [visible, setVisible] = useState(false);
const [error, setError] = useState(false);
const [code, setCode] = useState('');
const show = () => setVisible(true);
const hide = () => setVisible(false);
const joinRoom = async() => {
try {
await RocketChat.joinRoom(rid, code, t);
onJoin();
hide();
} catch (e) {
setError(true);
}
};
useImperativeHandle(ref, () => ({ show }));
return (
<Modal
transparent
avoidKeyboard
useNativeDriver
isVisible={visible}
hideModalContentWhileAnimating
>
<View style={styles.container} testID='join-code'>
<View style={[styles.content, isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], { backgroundColor: themes[theme].backgroundColor }]}>
<Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Insert_Join_Code')}</Text>
<TextInput
value={code}
theme={theme}
inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())}
returnKeyType='send'
autoCapitalize='none'
onChangeText={setCode}
onSubmitEditing={joinRoom}
placeholder={I18n.t('Join_Code')}
secureTextEntry
error={error && { error: 'error-code-invalid', reason: I18n.t('Code_or_password_invalid') }}
testID='join-code-input'
/>
<View style={styles.buttonContainer}>
<Button
title={I18n.t('Cancel')}
type='secondary'
style={styles.button}
backgroundColor={themes[theme].chatComponentBackground}
theme={theme}
testID='join-code-cancel'
onPress={hide}
/>
<Button
title={I18n.t('Join')}
type='primary'
style={styles.button}
theme={theme}
testID='join-code-submit'
onPress={joinRoom}
/>
</View>
</View>
</View>
</Modal>
);
}));
JoinCode.propTypes = {
rid: PropTypes.string,
t: PropTypes.string,
onJoin: PropTypes.func,
isMasterDetail: PropTypes.bool,
theme: PropTypes.string
};
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail
});
export default connect(mapStateToProps, null, null, { forwardRef: true })(JoinCode);

View File

@ -24,6 +24,7 @@ import MessageErrorActions from '../../containers/MessageErrorActions';
import MessageBox from '../../containers/MessageBox'; import MessageBox from '../../containers/MessageBox';
import ReactionPicker from './ReactionPicker'; import ReactionPicker from './ReactionPicker';
import UploadProgress from './UploadProgress'; import UploadProgress from './UploadProgress';
import JoinCode from './JoinCode';
import styles from './styles'; import styles from './styles';
import log, { logEvent, events } from '../../utils/log'; import log, { logEvent, events } from '../../utils/log';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
@ -73,7 +74,7 @@ const stateAttrsUpdate = [
'readOnly', 'readOnly',
'member' 'member'
]; ];
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor']; const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor', 'joinCodeRequired'];
class RoomView extends React.Component { class RoomView extends React.Component {
static propTypes = { static propTypes = {
@ -152,6 +153,7 @@ class RoomView extends React.Component {
this.messagebox = React.createRef(); this.messagebox = React.createRef();
this.list = React.createRef(); this.list = React.createRef();
this.joinCode = React.createRef();
this.mounted = false; this.mounted = false;
if (this.rid) { if (this.rid) {
this.sub = new RoomClass(this.rid); this.sub = new RoomClass(this.rid);
@ -711,6 +713,12 @@ class RoomView extends React.Component {
setLastOpen = lastOpen => this.setState({ lastOpen }); setLastOpen = lastOpen => this.setState({ lastOpen });
onJoin = () => {
this.internalSetState({
joined: true
});
}
joinRoom = async() => { joinRoom = async() => {
logEvent(events.ROOM_JOIN); logEvent(events.ROOM_JOIN);
try { try {
@ -719,11 +727,14 @@ class RoomView extends React.Component {
if (this.isOmnichannel) { if (this.isOmnichannel) {
await takeInquiry(room._id); await takeInquiry(room._id);
} else { } else {
await RocketChat.joinRoom(this.rid, this.t); const { joinCodeRequired } = room;
if (joinCodeRequired) {
this.joinCode.current?.show();
} else {
await RocketChat.joinRoom(this.rid, null, this.t);
this.onJoin();
}
} }
this.internalSetState({
joined: true
});
} catch (e) { } catch (e) {
log(e); log(e);
} }
@ -1086,6 +1097,13 @@ class RoomView extends React.Component {
onClose={this.onCloseReactionsModal} onClose={this.onCloseReactionsModal}
getCustomEmoji={this.getCustomEmoji} getCustomEmoji={this.getCustomEmoji}
/> />
<JoinCode
ref={this.joinCode}
onJoin={this.onJoin}
rid={rid}
t={t}
theme={theme}
/>
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -31,6 +31,10 @@ const data = {
channels: { channels: {
detoxpublic: { detoxpublic: {
name: 'detox-public' name: 'detox-public'
},
detoxpublicprotected: {
name: 'detox-public-protected',
joinCode: '123'
} }
}, },
groups: { groups: {

View File

@ -31,6 +31,10 @@ const data = {
channels: { channels: {
detoxpublic: { detoxpublic: {
name: 'detox-public' name: 'detox-public'
},
detoxpublicprotected: {
name: 'detox-public-protected',
joinCode: '123'
} }
}, },
groups: { groups: {

View File

@ -31,6 +31,10 @@ const data = {
channels: { channels: {
detoxpublic: { detoxpublic: {
name: 'detox-public' name: 'detox-public'
},
detoxpublicprotected: {
name: 'detox-public-protected',
joinCode: '123'
} }
}, },
groups: { groups: {

View File

@ -40,12 +40,14 @@ const createUser = async (username, password, name, email) => {
const createChannelIfNotExists = async (channelname) => { const createChannelIfNotExists = async (channelname) => {
console.log(`Creating public channel ${channelname}`) console.log(`Creating public channel ${channelname}`)
try { try {
await rocketchat.post('channels.create', { const room = await rocketchat.post('channels.create', {
"name": channelname "name": channelname
}) })
return room
} catch (createError) { } catch (createError) {
try { //Maybe it exists already? try { //Maybe it exists already?
await rocketchat.get(`channels.info?roomName=${channelname}`) const room = rocketchat.get(`channels.info?roomName=${channelname}`)
return room
} catch (infoError) { } catch (infoError) {
console.log(JSON.stringify(createError)) console.log(JSON.stringify(createError))
console.log(JSON.stringify(infoError)) console.log(JSON.stringify(infoError))
@ -71,6 +73,24 @@ const createGroupIfNotExists = async (groupname) => {
} }
} }
const changeChannelJoinCode = async (roomId, joinCode) => {
console.log(`Changing channel Join Code ${roomId}`)
try {
await rocketchat.post('method.call/saveRoomSettings', {
message: JSON.stringify({
method: 'saveRoomSettings',
params: [
roomId,
{ joinCode }
]
})
})
} catch (createError) {
console.log(JSON.stringify(createError))
throw "Failed to create protected channel"
}
}
const sendMessage = async (user, groupname, msg) => { const sendMessage = async (user, groupname, msg) => {
console.log(`Sending message to ${groupname}`) console.log(`Sending message to ${groupname}`)
try { try {
@ -96,7 +116,11 @@ const setup = async () => {
for (var channelKey in data.channels) { for (var channelKey in data.channels) {
if (data.channels.hasOwnProperty(channelKey)) { if (data.channels.hasOwnProperty(channelKey)) {
const channel = data.channels[channelKey] const channel = data.channels[channelKey]
await createChannelIfNotExists(channel.name) const { data: { channel: { _id } } } = await createChannelIfNotExists(channel.name)
if (channel.joinCode) {
await changeChannelJoinCode(_id, channel.joinCode);
}
} }
} }

View File

@ -0,0 +1,60 @@
const {
device, expect, element, by, waitFor
} = require('detox');
const data = require('../../data');
const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom } = require('../../helpers/app');
const testuser = data.users.regular
const room = data.channels.detoxpublicprotected.name
const joinCode = data.channels.detoxpublicprotected.joinCode
async function navigateToRoom() {
await searchRoom(room);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0)).toBeVisible().withTimeout(60000);
await element(by.id(`rooms-list-view-item-${ room }`)).atIndex(0).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
}
async function navigateToRoomActions() {
await element(by.id('room-view-header-actions')).tap();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
}
async function openJoinCode() {
await element(by.id('room-view-join-button')).tap();
await waitFor(element(by.id('join-code'))).toBeVisible().withTimeout(5000);
}
describe('Join public room', () => {
before(async() => {
await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
await navigateToLogin();
await login(testuser.username, testuser.password);
await navigateToRoom();
});
describe('Usage', async() => {
it('should tap join and ask for join code', async() => {
await openJoinCode();
})
it('should cancel join room', async() => {
await element(by.id('join-code-cancel')).tap();
await waitFor(element(by.id('join-code'))).toBeNotVisible().withTimeout(5000);
});
it('should join room', async() => {
await openJoinCode();
await element(by.id('join-code-input')).replaceText(joinCode);
await element(by.id('join-code-submit')).tap();
await waitFor(element(by.id('join-code'))).toBeNotVisible().withTimeout(5000);
await waitFor(element(by.id('messagebox'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('messagebox'))).toBeVisible();
await expect(element(by.id('room-view-join'))).toBeNotVisible();
});
it('should send message', async() => {
await mockMessage('message');
});
});
});