[MM-33766] Handle channel header call error (#5233)
* Apps bindings support (#5012) * Add redux related information * Add binding loading on channel refresh * Add channel header and post option bindings * Fix test * Minor fixes * Fix snapshots * Handle errors and show bindings only on main channel * Update Expand Levels keys and values to match latest changes * Add NAVIGATE call response handling. * Add more isolation to apps related code * Add defaults to send ephemeral * Update variable naming * Rename shouldProcessApps by a more meaningful appsEnabled * Embedded forms (#5169) * Add redux related information * Add binding loading on channel refresh * Add channel header and post option bindings * Fix test * Minor fixes * Fix snapshots * Handle errors and show bindings only on main channel * Update Expand Levels keys and values to match latest changes * Add NAVIGATE call response handling. * Add more isolation to apps related code * Add Embedded Forms * Fix snapshots * Add Embedded Forms * Improve naming, change the root element to be a binding and improve binding handling * Get post down on the buttons, remove unneeded variables and minor fixes from the review * Allow undefined bindings to fillBindingsInformation and add logging for error. * Address review feedback * Add App Forms (#5177) * Add App Forms * Address feedback and self review * Add dynamic select * Fixes and improvements * Add the ability to refresh on submit. * Use AppFormValue instead of redoing the type * Address feedback * [MM-31508] Rename URL to Path in Call (#5186) * Add user agent to call context (#5193) * Remove unneeded state reference (#5196) * Change user agent query on fetch bindings (#5201) * Add refresh websocket event to refetch bindings (#5194) * Add refresh websocket event to refetch bindings * Add missing changes * Declare socket event constant and separate the handler to a different function * Add binding validation on binding fetch (#5200) * Apps commands (#5107) * Add redux related information * Add binding loading on channel refresh * Add channel header and post option bindings * Fix test * Minor fixes * Fix snapshots * apps modals draft * Handle errors and show bindings only on main channel * Update Expand Levels keys and values to match latest changes * Add NAVIGATE call response handling. * Add more isolation to apps related code * reuse command parser throughout slash_suggestion component's lifecycle * fix prop and lint * using alert to show error message. need another way * duplicate import * types * types * types * rename file * copy webapp parser and test * dependencies moved. tests pass * move app command parser into its own folder * converted to typescript, all tests are passing * automated and manual tests work * lint * lint * remove fall throughs with blocks * types * doAppCall type * extract displayError to deps file * test types * lint * fix tests * unused import * PR feedback * fix imports and show errors * types * remove execute suggestion for mobile * watch feature flag * fix tests * change form text arugment behavior to show user input and not hint * return call response error in doAppCall * update tests to remove hint from text field suggestions * lint * use new base command structure * fix tests * wrap appsEnabled * typescript actions/command.ts * update app constants * PR feedback * fix error handling from doAppCall action * Use App CallRequest structure (#5212) * error handling * move test files * remove unused import Co-authored-by: Daniel Espino García <larkox@gmail.com> * Add feature flag (#5207) * Add feature flag * Simplify return * Add localization, call validation and use call type on subpath (#5221) * Add localization, call validation and use call type on subpath * Add more localization to the parser and bring fixes from webapp * Fix ephemerals and channel header / post options crashes * fix app command parser deps and alert messages * Improve suggestion handling * Fix test * Fix lint * Return errors as error * Address PR feedback * return error property * fix error string wordings Co-authored-by: Michael Kochell <6913320+mickmister@users.noreply.github.com> * leave the channel info screen when the call is successful. stay if there is an error * fix intl in tests Co-authored-by: Daniel Espino García <larkox@gmail.com> Co-authored-by: Ben Schumacher <ben.schumacher@mattermost.com> Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
parent
48d4775907
commit
985070ee71
5 changed files with 91 additions and 67 deletions
|
|
@ -503,7 +503,7 @@ exports[`channelInfo should match snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Bindings)
|
||||
<Connect(InjectIntl(Component))
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
|
@ -1035,7 +1035,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
|
|||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Bindings)
|
||||
<Connect(InjectIntl(Component))
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
|
@ -1425,7 +1425,7 @@ exports[`channelInfo should not include NotificationPreference for direct messag
|
|||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Bindings)
|
||||
<Connect(InjectIntl(Component))
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc42",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type Props = {
|
|||
theme: Theme;
|
||||
currentChannel: Channel;
|
||||
appsEnabled: boolean;
|
||||
intl: typeof intlShape;
|
||||
currentTeamId: string;
|
||||
actions: {
|
||||
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
|
||||
|
|
@ -28,7 +29,7 @@ type Props = {
|
|||
}
|
||||
}
|
||||
|
||||
const Bindings: React.FC<Props> = (props: Props) => {
|
||||
const Bindings: React.FC<Props> = injectIntl((props: Props) => {
|
||||
if (!props.appsEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -51,7 +52,7 @@ const Bindings: React.FC<Props> = (props: Props) => {
|
|||
{options}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Bindings;
|
||||
|
||||
|
|
@ -67,40 +68,60 @@ type OptionProps = {
|
|||
},
|
||||
}
|
||||
|
||||
const Option = injectIntl((props: OptionProps) => {
|
||||
const onPress = async () => {
|
||||
type OptionState = {
|
||||
submitting: boolean;
|
||||
}
|
||||
|
||||
class Option extends React.PureComponent<OptionProps, OptionState> {
|
||||
state = {
|
||||
submitting: false,
|
||||
};
|
||||
|
||||
onPress = async () => {
|
||||
const {binding, currentChannel, currentTeamId, intl} = this.props;
|
||||
const {doAppCall, sendEphemeralPost} = this.props.actions;
|
||||
|
||||
if (this.state.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!binding.call) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = createCallContext(
|
||||
binding.app_id,
|
||||
binding.location,
|
||||
props.currentChannel.id,
|
||||
props.currentChannel.team_id || props.currentTeamId,
|
||||
currentChannel.id,
|
||||
currentChannel.team_id || currentTeamId,
|
||||
);
|
||||
const call = createCallRequest(
|
||||
binding.call,
|
||||
context,
|
||||
);
|
||||
|
||||
const res = await props.actions.doAppCall(call, AppCallTypes.SUBMIT, props.intl);
|
||||
this.setState({submitting: true});
|
||||
|
||||
const res = await doAppCall(call, AppCallTypes.SUBMIT, intl);
|
||||
|
||||
this.setState({submitting: false});
|
||||
|
||||
if (res.error) {
|
||||
const errorResponse = res.error;
|
||||
const title = props.intl.formatMessage({
|
||||
const title = intl.formatMessage({
|
||||
id: 'mobile.general.error.title',
|
||||
defaultMessage: 'Error',
|
||||
});
|
||||
const errorMessage = errorResponse.error || props.intl.formatMessage({
|
||||
const errorMessage = errorResponse.error || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
});
|
||||
Alert.alert(title, errorMessage);
|
||||
dismissModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const callResp = res.data!;
|
||||
const ephemeral = (message: string) => props.actions.sendEphemeralPost(message, props.currentChannel.id);
|
||||
const ephemeral = (message: string) => sendEphemeralPost(message, currentChannel.id);
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.markdown) {
|
||||
|
|
@ -111,37 +132,40 @@ const Option = injectIntl((props: OptionProps) => {
|
|||
case AppCallResponseTypes.FORM:
|
||||
break;
|
||||
default: {
|
||||
const title = props.intl.formatMessage({
|
||||
const title = intl.formatMessage({
|
||||
id: 'mobile.general.error.title',
|
||||
defaultMessage: 'Error',
|
||||
});
|
||||
const errMessage = props.intl.formatMessage({
|
||||
const errMessage = intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {
|
||||
type: callResp.type,
|
||||
});
|
||||
Alert.alert(title, errMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dismissModal();
|
||||
};
|
||||
|
||||
const {binding, theme} = props;
|
||||
if (!binding.label) {
|
||||
return null;
|
||||
render() {
|
||||
const {binding, theme} = this.props;
|
||||
if (!binding.label) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
action={this.onPress}
|
||||
defaultMessage={binding.label}
|
||||
theme={theme}
|
||||
textId={binding.app_id + binding.location}
|
||||
image={binding.icon ? {uri: binding.icon} : null}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
action={onPress}
|
||||
defaultMessage={binding.label}
|
||||
theme={theme}
|
||||
textId={binding.app_id + binding.location}
|
||||
image={binding.icon ? {uri: binding.icon} : null}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ exports[`PostOptions should match snapshot, no option for system message to user
|
|||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Bindings)
|
||||
<Connect(InjectIntl(Component))
|
||||
closeWithAnimation={[Function]}
|
||||
post={
|
||||
Object {
|
||||
|
|
@ -436,7 +436,7 @@ exports[`PostOptions should match snapshot, showing Delete option only for syste
|
|||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Bindings)
|
||||
<Connect(InjectIntl(Component))
|
||||
closeWithAnimation={[Function]}
|
||||
post={
|
||||
Object {
|
||||
|
|
@ -680,7 +680,7 @@ exports[`PostOptions should match snapshot, showing all possible options 1`] = `
|
|||
}
|
||||
}
|
||||
/>
|
||||
<Connect(Bindings)
|
||||
<Connect(InjectIntl(Component))
|
||||
closeWithAnimation={[Function]}
|
||||
post={
|
||||
Object {
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@ type Props = {
|
|||
teamID: string,
|
||||
closeWithAnimation: () => void,
|
||||
appsEnabled: boolean,
|
||||
intl: typeof intlShape,
|
||||
actions: {
|
||||
doAppCall: (call: AppCallRequest, type: AppCallType, intl: any) => Promise<{data?: AppCallResponse, error?: AppCallResponse}>;
|
||||
sendEphemeralPost: (message: any, channelId?: string, parentId?: string) => Promise<ActionResult>;
|
||||
}
|
||||
}
|
||||
|
||||
const Bindings = (props: Props) => {
|
||||
const Bindings = injectIntl((props: Props) => {
|
||||
if (!props.appsEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -58,7 +59,7 @@ const Bindings = (props: Props) => {
|
|||
{options}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Bindings;
|
||||
|
||||
|
|
@ -76,9 +77,10 @@ type OptionProps = {
|
|||
},
|
||||
}
|
||||
|
||||
const Option = injectIntl((props: OptionProps) => {
|
||||
const onPress = async () => {
|
||||
const {closeWithAnimation, post, teamID} = props;
|
||||
class Option extends React.PureComponent<OptionProps> {
|
||||
onPress = async () => {
|
||||
const {closeWithAnimation, post, teamID, binding, intl} = this.props;
|
||||
const {doAppCall, sendEphemeralPost} = this.props.actions;
|
||||
|
||||
if (!binding.call) {
|
||||
return;
|
||||
|
|
@ -98,24 +100,25 @@ const Option = injectIntl((props: OptionProps) => {
|
|||
post: AppExpandLevels.ALL,
|
||||
},
|
||||
);
|
||||
const res = await props.actions?.doAppCall(call, AppCallTypes.SUBMIT, props.intl);
|
||||
|
||||
closeWithAnimation();
|
||||
const res = await doAppCall(call, AppCallTypes.SUBMIT, intl);
|
||||
if (res.error) {
|
||||
const errorResponse = res.error;
|
||||
const title = props.intl.formatMessage({
|
||||
const title = intl.formatMessage({
|
||||
id: 'mobile.general.error.title',
|
||||
defaultMessage: 'Error',
|
||||
});
|
||||
const errorMessage = errorResponse.error || props.intl.formatMessage({
|
||||
const errorMessage = errorResponse.error || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
});
|
||||
Alert.alert(title, errorMessage);
|
||||
closeWithAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
const callResp = (res as {data: AppCallResponse}).data;
|
||||
const ephemeral = (message: string) => props.actions.sendEphemeralPost(message, props.post.channel_id, props.post.root_id);
|
||||
const ephemeral = (message: string) => sendEphemeralPost(message, post.channel_id, post.root_id);
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.markdown) {
|
||||
|
|
@ -126,11 +129,11 @@ const Option = injectIntl((props: OptionProps) => {
|
|||
case AppCallResponseTypes.FORM:
|
||||
break;
|
||||
default: {
|
||||
const title = props.intl.formatMessage({
|
||||
const title = intl.formatMessage({
|
||||
id: 'mobile.general.error.title',
|
||||
defaultMessage: 'Error',
|
||||
});
|
||||
const errMessage = props.intl.formatMessage({
|
||||
const errMessage = intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {
|
||||
|
|
@ -139,21 +142,21 @@ const Option = injectIntl((props: OptionProps) => {
|
|||
Alert.alert(title, errMessage);
|
||||
}
|
||||
}
|
||||
|
||||
closeWithAnimation();
|
||||
};
|
||||
|
||||
const {binding, theme} = props;
|
||||
if (!binding.label) {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
const {binding, theme} = this.props;
|
||||
if (!binding.label) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PostOption
|
||||
icon={{uri: binding.icon}}
|
||||
text={binding.label}
|
||||
onPress={onPress}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<PostOption
|
||||
icon={{uri: binding.icon}}
|
||||
text={binding.label}
|
||||
onPress={this.onPress}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import {Alert} from 'react-native';
|
||||
import {shallow} from 'enzyme';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import PostOptions from './post_options';
|
||||
|
||||
jest.mock('react-intl');
|
||||
|
||||
describe('PostOptions', () => {
|
||||
const actions = {
|
||||
addReaction: jest.fn(),
|
||||
|
|
@ -55,12 +53,11 @@ describe('PostOptions', () => {
|
|||
};
|
||||
|
||||
function getWrapper(props = {}) {
|
||||
return shallow(
|
||||
return shallowWithIntl(
|
||||
<PostOptions
|
||||
{...baseProps}
|
||||
{...props}
|
||||
/>,
|
||||
{context: {intl: {formatMessage: ({defaultMessage}) => defaultMessage}}},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue