Make createCall function and related functions return an error message to be shown at a higher level (#5239)

This commit is contained in:
Daniel Espino García 2021-03-24 11:52:54 +01:00 committed by GitHub
parent 11a9590402
commit e75d3673cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 58 additions and 45 deletions

View file

@ -43,16 +43,13 @@ export function executeCommand(message: string, channelId: string, rootId: strin
if (appsAreEnabled) {
const parser = new AppCommandParser({dispatch, getState}, intl, args.channel_id, args.root_id);
if (parser.isAppCommand(msg)) {
const call = await parser.composeCallFromCommand(message);
const {call, errorMessage} = await parser.composeCallFromCommand(message);
const createErrorMessage = (errMessage: string) => {
return {error: {message: errMessage}};
};
if (!call) {
return createErrorMessage(intl.formatMessage({
id: 'mobile.commands.error_title',
defaultMessage: 'Error Executing Command',
}));
return createErrorMessage(errorMessage!);
}
const res = await dispatch(doAppCall(call, AppCallTypes.SUBMIT, intl));

View file

@ -879,7 +879,7 @@ describe('AppCommandParser', () => {
const cmd = '/jira issue create';
const values = {};
const call = await parser.composeCallFromCommand(cmd);
const {call} = await parser.composeCallFromCommand(cmd);
expect(call).toEqual({
...base,
raw_command: cmd,
@ -902,7 +902,7 @@ describe('AppCommandParser', () => {
project: '',
};
const call = await parser.composeCallFromCommand(cmd);
const {call} = await parser.composeCallFromCommand(cmd);
expect(call).toEqual({
...base,
expand: {},

View file

@ -553,35 +553,34 @@ export class AppCommandParser {
}
// composeCallFromCommand creates the form submission call
public composeCallFromCommand = async (command: string): Promise<AppCallRequest | null> => {
public composeCallFromCommand = async (command: string): Promise<{call: AppCallRequest | null, errorMessage?: string}> => {
let parsed = new ParsedCommand(command, this, this.intl);
const commandBindings = this.getCommandBindings();
if (!commandBindings) {
this.displayError(this.intl.formatMessage({
id: 'apps.error.parser.no_bindings',
defaultMessage: 'No command bindings.',
}));
return null;
return {call: null,
errorMessage: this.intl.formatMessage({
id: 'apps.error.parser.no_bindings',
defaultMessage: 'No command bindings.',
})};
}
parsed = await parsed.matchBinding(commandBindings, false);
parsed = parsed.parseForm(false);
if (parsed.state === ParseState.Error) {
this.displayError(parsed.errorMessage());
return null;
return {call: null, errorMessage: parsed.errorMessage()};
}
const missing = this.getMissingFields(parsed);
if (missing.length > 0) {
const missingStr = missing.map((f) => f.label).join(', ');
this.displayError(this.intl.formatMessage({
id: 'apps.error.command.field_missing',
defaultMessage: 'Required fields missing: `{fieldName}`.',
}, {
fieldName: missingStr,
}));
return null;
return {call: null,
errorMessage: this.intl.formatMessage({
id: 'apps.error.command.field_missing',
defaultMessage: 'Required fields missing: `{fieldName}`.',
}, {
fieldName: missingStr,
})};
}
return this.composeCallFromParsed(parsed);
@ -661,33 +660,41 @@ export class AppCommandParser {
}
// composeCallFromParsed creates the form submission call
composeCallFromParsed = async (parsed: ParsedCommand): Promise<AppCallRequest | null> => {
composeCallFromParsed = async (parsed: ParsedCommand): Promise<{call: AppCallRequest | null, errorMessage?: string}> => {
if (!parsed.binding) {
return null;
return {call: null,
errorMessage: this.intl.formatMessage({
id: 'apps.error.parser.missing_binding',
defaultMessage: 'Missing command bindings.',
})};
}
const call = parsed.form?.call || parsed.binding.call;
if (!call) {
return null;
return {call: null,
errorMessage: this.intl.formatMessage({
id: 'apps.error.parser.missing_call',
defaultMessage: 'Missing binding call.',
})};
}
const values: AppCallValues = parsed.values;
const ok = await this.expandOptions(parsed, values);
const {errorMessage} = await this.expandOptions(parsed, values);
if (!ok) {
return null;
if (errorMessage) {
return {call: null, errorMessage};
}
const context = this.getAppContext(parsed.binding.app_id);
return createCallRequest(call, context, {}, values, parsed.command);
return {call: createCallRequest(call, context, {}, values, parsed.command)};
}
expandOptions = async (parsed: ParsedCommand, values: AppCallValues) => {
expandOptions = async (parsed: ParsedCommand, values: AppCallValues): Promise<{errorMessage?: string}> => {
if (!parsed.form?.fields) {
return true;
return {};
}
let ok = true;
const errors: {[key: string]: string} = {};
await Promise.all(parsed.form.fields.map(async (f) => {
if (!values[f.name]) {
return;
@ -699,14 +706,13 @@ export class AppCommandParser {
case AppFieldTypes.STATIC_SELECT: {
const option = f.options?.find((o) => (o.value === values[f.name]));
if (!option) {
ok = false;
this.displayError(this.intl.formatMessage({
errors[f.name] = this.intl.formatMessage({
id: 'apps.error.command.unknown_option',
defaultMessage: 'Unknown option for field `{fieldName}`: `{option}`.',
}, {
fieldName: f.name,
option: values[f.name],
}));
});
return;
}
values[f.name] = option;
@ -721,14 +727,13 @@ export class AppCommandParser {
if (!user) {
const dispatchResult = await this.store.dispatch(getUserByUsername(userName) as any);
if ('error' in dispatchResult) {
ok = false;
this.displayError(this.intl.formatMessage({
errors[f.name] = this.intl.formatMessage({
id: 'apps.error.command.unknown_user',
defaultMessage: 'Unknown user for field `{fieldName}`: `{option}`.',
}, {
fieldName: f.name,
option: values[f.name],
}));
});
return;
}
user = dispatchResult.data;
@ -745,14 +750,13 @@ export class AppCommandParser {
if (!channel) {
const dispatchResult = await this.store.dispatch(getChannelByNameAndTeamName(getCurrentTeam(this.store.getState()).name, channelName) as any);
if ('error' in dispatchResult) {
ok = false;
this.displayError(this.intl.formatMessage({
errors[f.name] = this.intl.formatMessage({
id: 'apps.error.command.unknown_channel',
defaultMessage: 'Unknown channel for field `{fieldName}`: `{option}`.',
}, {
fieldName: f.name,
option: values[f.name],
}));
});
return;
}
channel = dispatchResult.data;
@ -763,7 +767,15 @@ export class AppCommandParser {
}
}));
return ok;
if (Object.keys(errors).length === 0) {
return {};
}
let errorMessage = '';
Object.keys(errors).forEach((v) => {
errorMessage = errorMessage + errors[v] + '\n';
});
return {errorMessage};
}
// decorateSuggestionComplete applies the necessary modifications for a suggestion to be processed
@ -1093,11 +1105,13 @@ export class AppCommandParser {
}));
}
const call = await this.composeCallFromParsed(parsed);
const {call, errorMessage} = await this.composeCallFromParsed(parsed);
if (!call) {
return this.makeSuggestionError(this.intl.formatMessage({
id: 'apps.error.lookup.error_preparing_request',
defaultMessage: 'Error preparing lookup request.',
defaultMessage: 'Error preparing lookup request: {errorMessage}',
}, {
errorMessage,
}));
}
call.selected_field = f.name;

View file

@ -23,9 +23,11 @@
"apps.error.form.refresh": "There has been an error fetching the select fields. Contact the app developer. Details: {details}",
"apps.error.form.refresh_no_refresh": "Called refresh on no refresh field.",
"apps.error.form.submit.pretext": "There has been an error submitting the modal. Contact the app developer. Details: {details}",
"apps.error.lookup.error_preparing_request": "Error preparing lookup request.",
"apps.error.lookup.error_preparing_request": "Error preparing lookup request: {errorMessage}",
"apps.error.parser": "Parsing error: {error}.\n```\n{command}\n{space}^\n```",
"apps.error.parser.empty_value": "empty values are not allowed",
"apps.error.parser.missing_binding": "Missing command bindings.",
"apps.error.parser.missing_call": "Missing binding call.",
"apps.error.parser.missing_field_value": "Field value is missing.",
"apps.error.parser.missing_quote": "Matching double quote expected before end of input.",
"apps.error.parser.missing_tick": "Matching tick quote expected before end of input.",