Fix add command in backstage (#24043)

This commit is contained in:
Elias Nahum 2023-07-18 09:03:52 -04:00 committed by GitHub
parent 150c6e7aef
commit 66c2837d2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 513 additions and 12 deletions

View File

@ -471,6 +471,475 @@ exports[`components/integrations/AbstractCommand should match snapshot 1`] = `
</div>
`;
exports[`components/integrations/AbstractCommand should match snapshot when header/footer/loading is a string 1`] = `
<div
className="backstage-content row"
>
<BackstageHeader>
<Link
to="/test/integrations/commands"
>
<MemoizedFormattedMessage
defaultMessage="Slash Commands"
id="installed_command.header"
/>
</Link>
<span>
Header as string
</span>
</BackstageHeader>
<div
className="backstage-form"
>
<form
className="form-horizontal"
onSubmit={[Function]}
>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="displayName"
>
<MemoizedFormattedMessage
defaultMessage="Title"
id="add_command.displayName"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<input
className="form-control"
id="displayName"
maxLength={64}
onChange={[Function]}
type="text"
value="display_name"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Specify a title, of up to 64 characters, for the slash command settings page."
id="add_command.displayName.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="description"
>
<MemoizedFormattedMessage
defaultMessage="Description"
id="add_command.description"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<input
className="form-control"
id="description"
maxLength={128}
onChange={[Function]}
type="text"
value="description"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Describe your slash command."
id="add_command.description.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="trigger"
>
<MemoizedFormattedMessage
defaultMessage="Command Trigger Word"
id="add_command.trigger"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<LocalizedInput
className="form-control"
id="trigger"
maxLength={128}
onChange={[Function]}
placeholder={
Object {
"defaultMessage": "Command trigger e.g. \\"hello\\" not including the slash",
"id": "add_command.trigger.placeholder",
}
}
type="text"
value="trigger"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Specify a trigger word that is not a built-in command, does not contain spaces, and does not begin with the slash character."
id="add_command.trigger.help"
/>
</div>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Examples: client, employee, patient, weather"
id="add_command.trigger.helpExamples"
/>
</div>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Reserved: {link}"
id="add_command.trigger.helpReserved"
values={
Object {
"link": <ExternalLink
href="https://mattermost.com/pl/custom-slash-commands"
location="abstract_command"
>
<Memo(MemoizedFormattedMessage)
defaultMessage="See built-in slash commands"
id="add_command.trigger.helpReservedLinkText"
/>
</ExternalLink>,
}
}
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="url"
>
<MemoizedFormattedMessage
defaultMessage="Request URL"
id="add_command.url"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<LocalizedInput
className="form-control"
id="url"
maxLength={1024}
onChange={[Function]}
placeholder={
Object {
"defaultMessage": "Must start with http:// or https://",
"id": "add_command.url.placeholder",
}
}
type="text"
value="https://google.com/command"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Specify the callback URL to receive the HTTP POST or GET event request when the slash command is run."
id="add_command.url.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="method"
>
<MemoizedFormattedMessage
defaultMessage="Request Method"
id="add_command.method"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<select
className="form-control"
id="method"
onChange={[Function]}
value="G"
>
<option
value="P"
>
POST
</option>
<option
value="G"
>
GET
</option>
</select>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="Specify the type of request, either POST or GET, sent to the endpoint that Mattermost hits to reach your application."
id="add_command.method.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="username"
>
<MemoizedFormattedMessage
defaultMessage="Response Username"
id="add_command.username"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<LocalizedInput
className="form-control"
id="username"
maxLength={64}
onChange={[Function]}
placeholder={
Object {
"defaultMessage": "Username",
"id": "add_command.username.placeholder",
}
}
type="text"
value="username"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="(Optional) Specify the name to use when posting responses for this slash command. Usernames can be up to 22 characters, and contain lowercase letters, numbers, and the symbols \\\\\\"-\\\\\\", \\\\\\"_\\\\\\", and \\\\\\".\\\\\\". If left blank, your Mattermost username is used."
id="add_command.username.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="iconUrl"
>
<MemoizedFormattedMessage
defaultMessage="Response Icon"
id="add_command.iconUrl"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<LocalizedInput
className="form-control"
id="iconUrl"
maxLength={1024}
onChange={[Function]}
placeholder={
Object {
"defaultMessage": "https://www.example.com/myicon.png",
"id": "add_command.iconUrl.placeholder",
}
}
type="text"
value="https://google.com/icon"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="(Optional) Enter the URL of a .png or .jpg file to use as the icon when posting responses to this slash command. The file must be at least 128 pixels by 128 pixels. If left blank, your profile picture is used."
id="add_command.iconUrl.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="autocomplete"
>
<MemoizedFormattedMessage
defaultMessage="Autocomplete"
id="add_command.autocomplete"
/>
</label>
<div
className="col-md-5 col-sm-8 checkbox"
>
<input
checked={true}
id="autocomplete"
onChange={[Function]}
type="checkbox"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="(Optional) Show your slash command on the autocomplete list when someone types \\"/\\" in the input box."
id="add_command.autocomplete.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="autocompleteHint"
>
<MemoizedFormattedMessage
defaultMessage="Autocomplete Hint"
id="add_command.autocompleteHint"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<LocalizedInput
className="form-control"
id="autocompleteHint"
maxLength={1024}
onChange={[Function]}
placeholder={
Object {
"defaultMessage": "Example: [Patient Name]",
"id": "add_command.autocompleteHint.placeholder",
}
}
type="text"
value="auto_complete_hint"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="(Optional) Specify the arguments associated with your slash command. These are displayed as help on the autocomplete list."
id="add_command.autocompleteHint.help"
/>
</div>
</div>
</div>
<div
className="form-group"
>
<label
className="control-label col-sm-4"
htmlFor="autocompleteDescription"
>
<MemoizedFormattedMessage
defaultMessage="Autocomplete Description"
id="add_command.autocompleteDescription"
/>
</label>
<div
className="col-md-5 col-sm-8"
>
<LocalizedInput
className="form-control"
id="description"
maxLength={128}
onChange={[Function]}
placeholder={
Object {
"defaultMessage": "Example: \\"Returns search results for patient records\\"",
"id": "add_command.autocompleteDescription.placeholder",
}
}
type="text"
value="auto_complete_desc"
/>
<div
className="form__help"
>
<MemoizedFormattedMessage
defaultMessage="(Optional) Describe your slash command for the autocomplete list."
id="add_command.autocompleteDescription.help"
/>
</div>
</div>
</div>
<div
className="backstage-form__footer"
>
<FormError
error={null}
errors={
Array [
"",
null,
]
}
type="backstage"
/>
<Link
className="btn btn-link btn-sm"
to="/test/integrations/commands"
>
<MemoizedFormattedMessage
defaultMessage="Cancel"
id="add_command.cancel"
/>
</Link>
<SpinnerButton
className="btn btn-primary"
id="saveCommand"
onClick={[Function]}
spinning={false}
spinningText="Loading as string"
type="submit"
>
<span>
Footer as string
</span>
</SpinnerButton>
<div>
renderExtra
</div>
</div>
</form>
</div>
</div>
`;
exports[`components/integrations/AbstractCommand should match snapshot, displays client error 1`] = `
<div
className="backstage-content row"

View File

@ -60,6 +60,18 @@ describe('components/integrations/AbstractCommand', () => {
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot when header/footer/loading is a string', () => {
const wrapper = shallow<AbstractCommand>(
<AbstractCommand
{...baseProps}
header='Header as string'
loading={'Loading as string'}
footer={'Footer as string'}
/>,
);
expect(wrapper).toMatchSnapshot();
});
test('should match snapshot, displays client error', () => {
const newSeverError = 'server error';
const props = {...baseProps, serverError: newSeverError};

View File

@ -31,17 +31,17 @@ type Props = {
/**
* The header text to render, has id and defaultMessage
*/
header: MessageDescriptor;
header: MessageDescriptor | string;
/**
* The footer text to render, has id and defaultMessage
*/
footer: MessageDescriptor;
footer: MessageDescriptor | string;
/**
* The spinner loading text to render, has id and defaultMessage
*/
loading: MessageDescriptor;
loading: MessageDescriptor | string;
/**
* Any extra component/node to render
@ -103,6 +103,32 @@ export default class AbstractCommand extends React.PureComponent<Props, State> {
};
};
getBackstageHeader = () => {
if (typeof this.props.header === 'string') {
return <span>{this.props.header}</span>;
}
return (
<FormattedMessage
id={this.props.header.id}
defaultMessage={this.props.header.defaultMessage}
/>
);
};
getBackstageFooter = () => {
if (typeof this.props.footer === 'string') {
return <span>{this.props.footer}</span>;
}
return (
<FormattedMessage
id={this.props.footer.id}
defaultMessage={this.props.footer.defaultMessage}
/>
);
};
handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@ -362,10 +388,7 @@ export default class AbstractCommand extends React.PureComponent<Props, State> {
defaultMessage='Slash Commands'
/>
</Link>
<FormattedMessage
id={this.props.header.id}
defaultMessage={this.props.header.defaultMessage}
/>
{this.getBackstageHeader()}
</BackstageHeader>
<div className='backstage-form'>
<form
@ -640,14 +663,11 @@ export default class AbstractCommand extends React.PureComponent<Props, State> {
className='btn btn-primary'
type='submit'
spinning={this.state.saving}
spinningText={Utils.localizeMessage(this.props.loading?.id ?? '', this.props.loading?.defaultMessage as string)}
spinningText={typeof this.props.loading === 'string' ? this.props.loading : Utils.localizeMessage(this.props.loading?.id ?? '', this.props.loading?.defaultMessage as string)}
onClick={this.handleSubmit}
id='saveCommand'
>
<FormattedMessage
id={this.props.footer.id}
defaultMessage={this.props.footer.defaultMessage}
/>
{this.getBackstageFooter()}
</SpinnerButton>
{this.props.renderExtra}
</div>