MM-15218 Open as alert dialog when interactive message is without element (#3088)

* open as alert dialog when interactive message is without element

* add missing translations
This commit is contained in:
Saturnino Abril 2019-08-13 22:16:14 +08:00 committed by Elias Nahum
parent 7410e3c266
commit ca61faafd5
4 changed files with 132 additions and 10 deletions

View file

@ -4,6 +4,8 @@
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {submitInteractiveDialog} from 'mattermost-redux/actions/integrations';
import {showModal} from 'app/actions/navigation';
import InteractiveDialogController from './interactive_dialog_controller';
@ -11,7 +13,7 @@ import InteractiveDialogController from './interactive_dialog_controller';
function mapStateToProps(state) {
return {
triggerId: state.entities.integrations.dialogTriggerId,
dialog: state.entities.integrations.dialog || {},
dialogData: state.entities.integrations.dialog || {},
};
}
@ -19,6 +21,7 @@ function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
showModal,
submitInteractiveDialog,
}, dispatch),
};
}

View file

@ -4,14 +4,17 @@
import {PureComponent} from 'react';
import PropTypes from 'prop-types';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import {Alert} from 'react-native';
import {intlShape} from 'react-intl';
export default class InteractiveDialogController extends PureComponent {
static propTypes = {
actions: PropTypes.shape({
showModal: PropTypes.func.isRequired,
submitInteractiveDialog: PropTypes.func.isRequired,
}).isRequired,
triggerId: PropTypes.string,
dialog: PropTypes.object,
dialogData: PropTypes.object,
theme: PropTypes.object,
};
@ -23,14 +26,18 @@ export default class InteractiveDialogController extends PureComponent {
});
}
static contextTypes = {
intl: intlShape,
};
componentDidUpdate(prevProps) {
const {actions, triggerId} = this.props;
const {triggerId} = this.props;
if (!triggerId) {
return;
}
const dialogData = this.props.dialog || {};
const prevDialogData = prevProps.dialog || {};
const dialogData = this.props.dialogData || {};
const prevDialogData = prevProps.dialogData || {};
if (prevProps.triggerId === triggerId && dialogData.trigger_id === prevDialogData.trigger_id) {
return;
}
@ -43,9 +50,30 @@ export default class InteractiveDialogController extends PureComponent {
return;
}
const screen = 'InteractiveDialog';
const title = dialogData.dialog.title;
const passProps = {};
if (dialogData.dialog.elements && dialogData.dialog.elements.length > 0) {
this.showInteractiveDialogScreen(dialogData.dialog);
} else {
this.showAlertDialog(dialogData.dialog, dialogData.url);
}
}
showAlertDialog(dialog, url) {
const {formatMessage} = this.context.intl;
Alert.alert(
dialog.title,
'',
[{
text: formatMessage({id: 'mobile.alert_dialog.alertCancel', defaultMessage: 'Cancel'}),
onPress: () => this.handleCancel(dialog, url),
}, {
text: dialog.submit_label,
onPress: () => this.props.actions.submitInteractiveDialog({...dialog, url}),
}],
);
}
showInteractiveDialogScreen = (dialog) => {
const options = {
topBar: {
leftButtons: [{
@ -55,12 +83,18 @@ export default class InteractiveDialogController extends PureComponent {
rightButtons: [{
id: 'submit-dialog',
showAsAction: 'always',
text: dialogData.dialog.submit_label,
text: dialog.submit_label,
}],
},
};
actions.showModal(screen, title, passProps, options);
this.props.actions.showModal('InteractiveDialog', dialog.title, null, options);
}
handleCancel = (dialog, url) => {
if (dialog.notify_on_cancel) {
this.props.actions.submitInteractiveDialog({...dialog, url, cancelled: true});
}
}
render() {

View file

@ -0,0 +1,82 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import InteractiveDialogController from './interactive_dialog_controller';
jest.mock('react-intl');
jest.mock('react-native-vector-icons/MaterialIcons', () => ({
getImageSource: jest.fn().mockResolvedValue({}),
}));
describe('InteractiveDialogController', () => {
test('should open interactive dialog as alert or screen depending on with or without element', () => {
let baseProps = getBaseProps('trigger_id_1');
const wrapper = shallow(
<InteractiveDialogController {...baseProps}/>,
{context: {intl: {formatMessage: jest.fn()}}},
);
const instance = wrapper.instance();
instance.showAlertDialog = jest.fn();
instance.showInteractiveDialogScreen = jest.fn();
expect(instance.showAlertDialog).toHaveBeenCalledTimes(0);
expect(instance.showInteractiveDialogScreen).toHaveBeenCalledTimes(0);
baseProps = getBaseProps('trigger_id_2');
wrapper.setProps({...baseProps});
expect(instance.showAlertDialog).toHaveBeenCalledTimes(1);
expect(instance.showAlertDialog).toHaveBeenCalledWith(baseProps.dialogData.dialog, baseProps.dialogData.url);
expect(instance.showInteractiveDialogScreen).toHaveBeenCalledTimes(0);
const elements = [{
data_source: '',
default: '',
display_name: 'Number',
help_text: '',
max_length: 0,
min_length: 0,
name: 'somenumber',
optional: false,
options: null,
placeholder: '',
subtype: 'number',
type: 'text',
}];
baseProps = getBaseProps('trigger_id_3', elements);
wrapper.setProps({...baseProps});
expect(instance.showAlertDialog).toHaveBeenCalledTimes(1);
expect(instance.showInteractiveDialogScreen).toHaveBeenCalledTimes(1);
expect(instance.showInteractiveDialogScreen).toHaveBeenCalledWith(baseProps.dialogData.dialog);
});
});
function getBaseProps(triggerId, elements) {
const dialogData = {
dialog: {
callback_id: 'somecallbackid',
elements,
icon_url: 'icon_url',
notify_on_cancel: true,
state: 'somestate',
submit_label: 'Submit Test',
title: 'Dialog Test',
},
trigger_id: triggerId,
url: 'https://localhost:8065/dialog_submit',
};
return {
actions: {
showModal: jest.fn(),
submitInteractiveDialog: jest.fn(),
},
triggerId,
dialogData,
theme: {},
};
}

View file

@ -137,6 +137,7 @@
"mobile.advanced_settings.delete_title": "Delete Documents & Data",
"mobile.advanced_settings.timezone": "Timezone",
"mobile.advanced_settings.title": "Advanced Settings",
"mobile.alert_dialog.alertCancel": "Cancel",
"mobile.android.camera_permission_denied_description": "To take photos and videos with your camera, please change your permission settings.",
"mobile.android.camera_permission_denied_title": "Camera access is required",
"mobile.android.permission_denied_dismiss": "Dismiss",
@ -357,6 +358,8 @@
"mobile.post_textbox.empty.title": "Empty Message",
"mobile.post_textbox.entire_channel.cancel": "Cancel",
"mobile.post_textbox.entire_channel.confirm": "Confirm",
"mobile.post_textbox.entire_channel.message": "By using @all or @channel you are about to send notifications to {totalMembers} people. Are you sure you want to do this?",
"mobile.post_textbox.entire_channel.message.with_timezones": "By using @all or @channel you are about to send notifications to {totalMembers} people in {timezones, number} {timezones, plural, one {timezone} other {timezones}}. Are you sure you want to do this?",
"mobile.post_textbox.entire_channel.title": "Confirm sending notifications to entire channel",
"mobile.post_textbox.uploadFailedDesc": "Some attachments failed to upload to the server. Are you sure you want to post the message?",
"mobile.post_textbox.uploadFailedTitle": "Attachment failure",