summaryrefslogtreecommitdiffstats
path: root/webapp
diff options
context:
space:
mode:
authorHarrison Healey <harrisonmhealey@gmail.com>2016-04-05 09:29:01 -0400
committerChristopher Speller <crspeller@gmail.com>2016-04-05 09:29:01 -0400
commitb3edd32aee47a0b123870de58664600acc17087b (patch)
tree41840177672480ff428f279437a5a08a6eccaeb6 /webapp
parentc12d997f248c143b7746d07a3c2ce9b58a3ecd5e (diff)
downloadchat-b3edd32aee47a0b123870de58664600acc17087b.tar.gz
chat-b3edd32aee47a0b123870de58664600acc17087b.tar.bz2
chat-b3edd32aee47a0b123870de58664600acc17087b.zip
PLT-1750 Moved slash commands to backstage
* Added slash commands to InstalledIntegrations page * Reset installed integration type filter if there is no longer any integrations of the selected type * Added pages to backstage to add slash commands * Cleaned up internationalization for slash commands * Added ability to regen slash command tokens from backstage * Removed Integrations tab from UserSettings
Diffstat (limited to 'webapp')
-rw-r--r--webapp/components/backstage/add_command.jsx509
-rw-r--r--webapp/components/backstage/add_integration.jsx22
-rw-r--r--webapp/components/backstage/backstage_sidebar.jsx9
-rw-r--r--webapp/components/backstage/installed_command.jsx97
-rw-r--r--webapp/components/backstage/installed_incoming_webhook.jsx10
-rw-r--r--webapp/components/backstage/installed_integrations.jsx104
-rw-r--r--webapp/components/user_settings/manage_command_hooks.jsx681
-rw-r--r--webapp/components/user_settings/user_settings.jsx15
-rw-r--r--webapp/components/user_settings/user_settings_integrations.jsx126
-rw-r--r--webapp/components/user_settings/user_settings_modal.jsx17
-rw-r--r--webapp/i18n/en.json75
-rw-r--r--webapp/i18n/es.json63
-rw-r--r--webapp/i18n/fr.json62
-rw-r--r--webapp/i18n/pt.json65
-rw-r--r--webapp/root.jsx9
-rw-r--r--webapp/stores/integration_store.jsx61
-rw-r--r--webapp/utils/async_client.jsx77
-rw-r--r--webapp/utils/constants.jsx4
18 files changed, 994 insertions, 1012 deletions
diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx
new file mode 100644
index 000000000..93ff66271
--- /dev/null
+++ b/webapp/components/backstage/add_command.jsx
@@ -0,0 +1,509 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as AsyncClient from 'utils/async_client.jsx';
+import {browserHistory} from 'react-router';
+import * as Utils from 'utils/utils.jsx';
+
+import {FormattedMessage} from 'react-intl';
+import FormError from 'components/form_error.jsx';
+import {Link} from 'react-router';
+import SpinnerButton from 'components/spinner_button.jsx';
+
+const REQUEST_POST = 'P';
+const REQUEST_GET = 'G';
+
+export default class AddCommand extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleSubmit = this.handleSubmit.bind(this);
+
+ this.updateDisplayName = this.updateDisplayName.bind(this);
+ this.updateDescription = this.updateDescription.bind(this);
+ this.updateTrigger = this.updateTrigger.bind(this);
+ this.updateUrl = this.updateUrl.bind(this);
+ this.updateMethod = this.updateMethod.bind(this);
+ this.updateUsername = this.updateUsername.bind(this);
+ this.updateIconUrl = this.updateIconUrl.bind(this);
+ this.updateAutocomplete = this.updateAutocomplete.bind(this);
+ this.updateAutocompleteHint = this.updateAutocompleteHint.bind(this);
+ this.updateAutocompleteDescription = this.updateAutocompleteDescription.bind(this);
+
+ this.state = {
+ displayName: '',
+ description: '',
+ trigger: '',
+ url: '',
+ method: REQUEST_POST,
+ username: '',
+ iconUrl: '',
+ autocomplete: false,
+ autocompleteHint: '',
+ autocompleteDescription: '',
+ saving: false,
+ serverError: '',
+ clientError: null
+ };
+ }
+
+ handleSubmit(e) {
+ e.preventDefault();
+
+ if (this.state.saving) {
+ return;
+ }
+
+ this.setState({
+ saving: true,
+ serverError: '',
+ clientError: ''
+ });
+
+ const command = {
+ display_name: this.state.displayName,
+ description: this.state.description,
+ trigger: this.state.trigger.trim(),
+ url: this.state.url.trim(),
+ method: this.state.method,
+ username: this.state.username,
+ icon_url: this.state.iconUrl,
+ auto_complete: this.state.autocomplete
+ };
+
+ if (command.auto_complete) {
+ command.auto_complete_desc = this.state.autocompleteDescription;
+ command.auto_complete_hint = this.state.autocompleteHint;
+ }
+
+ if (!command.trigger) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.triggerRequired'
+ defaultMessage='A trigger word is required'
+ />
+ )
+ });
+ }
+
+ if (!command.url) {
+ this.setState({
+ saving: false,
+ clientError: (
+ <FormattedMessage
+ id='add_command.urlRequired'
+ defaultMessage='A request URL is required'
+ />
+ )
+ });
+ }
+
+ AsyncClient.addCommand(
+ command,
+ () => {
+ browserHistory.push('/settings/integrations/installed');
+ },
+ (err) => {
+ this.setState({
+ saving: false,
+ serverError: err.message
+ });
+ }
+ );
+ }
+
+ updateDisplayName(e) {
+ this.setState({
+ displayName: e.target.value
+ });
+ }
+
+ updateDescription(e) {
+ this.setState({
+ description: e.target.value
+ });
+ }
+
+ updateTrigger(e) {
+ this.setState({
+ trigger: e.target.value
+ });
+ }
+
+ updateUrl(e) {
+ this.setState({
+ url: e.target.value
+ });
+ }
+
+ updateMethod(e) {
+ this.setState({
+ method: e.target.value
+ });
+ }
+
+ updateUsername(e) {
+ this.setState({
+ username: e.target.value
+ });
+ }
+
+ updateIconUrl(e) {
+ this.setState({
+ iconUrl: e.target.value
+ });
+ }
+
+ updateAutocomplete(e) {
+ this.setState({
+ autocomplete: e.target.checked
+ });
+ }
+
+ updateAutocompleteHint(e) {
+ this.setState({
+ autocompleteHint: e.target.value
+ });
+ }
+
+ updateAutocompleteDescription(e) {
+ this.setState({
+ autocompleteDescription: e.target.value
+ });
+ }
+
+ render() {
+ let autocompleteFields = null;
+ if (this.state.autocomplete) {
+ autocompleteFields = [(
+ <div
+ key='autocompleteHint'
+ className='form-group'
+ >
+ <label
+ className='control-label col-sm-3'
+ htmlFor='autocompleteHint'
+ >
+ <FormattedMessage
+ id='add_command.autocompleteHint'
+ defaultMessage='Autocomplete Hint'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='autocompleteHint'
+ type='text'
+ maxLength='1024'
+ className='form-control'
+ value={this.state.autocompleteHint}
+ onChange={this.updateAutocompleteHint}
+ placeholder={Utils.localizeMessage('add_command.autocompleteHint.placeholder', 'Example: [Patient Name]')}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.autocompleteDescription.help'
+ defaultMessage='Optional hint in the autocomplete list about command parameters'
+ />
+ </div>
+ </div>
+ </div>
+ ),
+ (
+ <div
+ key='autocompleteDescription'
+ className='form-group'
+ >
+ <label
+ className='control-label col-sm-3'
+ htmlFor='autocompleteDescription'
+ >
+ <FormattedMessage
+ id='add_command.autocompleteDescription'
+ defaultMessage='Autocomplete Description'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='description'
+ type='text'
+ maxLength='128'
+ className='form-control'
+ value={this.state.autocompleteDescription}
+ onChange={this.updateAutocompleteDescription}
+ placeholder={Utils.localizeMessage('add_command.autocompleteDescription.placeholder', 'Example: "Returns search results for patient records"')}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.autocompleteDescription.help'
+ defaultMessage='Optional short description of slash command for the autocomplete list.'
+ />
+ </div>
+ </div>
+ </div>
+ )];
+ }
+
+ return (
+ <div className='backstage-content row'>
+ <div className='add-command'>
+ <div className='backstage-header'>
+ <h1>
+ <FormattedMessage
+ id='add_command.header'
+ defaultMessage='Add Slash Command'
+ />
+ </h1>
+ </div>
+ </div>
+ <div className='backstage-form'>
+ <form className='form-horizontal'>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='displayName'
+ >
+ <FormattedMessage
+ id='add_command.displayName'
+ defaultMessage='Display Name'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='displayName'
+ type='text'
+ maxLength='64'
+ className='form-control'
+ value={this.state.displayName}
+ onChange={this.updateDisplayName}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='description'
+ >
+ <FormattedMessage
+ id='add_command.description'
+ defaultMessage='Description'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='description'
+ type='text'
+ maxLength='128'
+ className='form-control'
+ value={this.state.description}
+ onChange={this.updateDescription}
+ />
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='trigger'
+ >
+ <FormattedMessage
+ id='add_command.trigger'
+ defaultMessage='Command Trigger Word'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='trigger'
+ type='text'
+ maxLength='128'
+ className='form-control'
+ value={this.state.trigger}
+ onChange={this.updateTrigger}
+ placeholder={Utils.localizeMessage('add_command.trigger.placeholder', 'Command trigger e.g. "hello" not including the slash')}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.trigger.help1'
+ defaultMessage='Examples: /patient, /client /employee'
+ />
+ </div>
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.trigger.help2'
+ defaultMessage='Reserved: /echo, /join, /logout, /me, /shrug'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='url'
+ >
+ <FormattedMessage
+ id='add_command.url'
+ defaultMessage='Request URL'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='url'
+ type='text'
+ maxLength='1024'
+ className='form-control'
+ value={this.state.url}
+ onChange={this.updateUrl}
+ placeholder={Utils.localizeMessage('add_command.url.placeholder', 'Must start with http:// or https://')}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.url.help'
+ defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='method'
+ >
+ <FormattedMessage
+ id='add_command.method'
+ defaultMessage='Request Method'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <select
+ id='method'
+ className='form-control'
+ value={this.state.method}
+ onChange={this.updateMethod}
+ >
+ <option value={REQUEST_POST}>
+ {Utils.localizeMessage('add_command.method.post', 'POST')}
+ </option>
+ <option value={REQUEST_GET}>
+ {Utils.localizeMessage('add_command.method.get', 'GET')}
+ </option>
+ </select>
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.method.help'
+ defaultMessage='The type of command request issued to the Request URL.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-lavel col-sm-3'
+ htmlFor='username'
+ >
+ <FormattedMessage
+ id='add_command.username'
+ defaultMessage='Response Username'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='username'
+ type='text'
+ maxLength='64'
+ className='form-control'
+ value={this.state.username}
+ onChange={this.updateUsername}
+ placholder={Utils.localizeMessage('add_command.username.placeholder', 'Username')}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.username.help'
+ defaultMessage='Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and the symbols "-", "_", and ".".'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='iconUrl'
+ >
+ <FormattedMessage
+ id='add_command.iconUrl'
+ defaultMessage='Response Icon'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ id='iconUrl'
+ type='text'
+ maxLength='1024'
+ className='form-control'
+ value={this.state.iconUrl}
+ onChange={this.updateIconUrl}
+ placeholder={Utils.localizeMessage('add_command.iconUrl.placeholder', 'https://www.example.com/myicon.png')}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.iconUrl.help'
+ defaultMessage='Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
+ />
+ </div>
+ </div>
+ </div>
+ <div className='form-group'>
+ <label
+ className='control-label col-sm-3'
+ htmlFor='autocomplete'
+ >
+ <FormattedMessage
+ id='add_command.autocomplete'
+ defaultMessage='Autocomplete'
+ />
+ </label>
+ <div className='col-md-5 col-sm-9'>
+ <input
+ type='checkbox'
+ checked={this.state.autocomplete}
+ onChange={this.updateAutocomplete}
+ />
+ <div className='add-integration__help'>
+ <FormattedMessage
+ id='add_command.autocomplete.help'
+ defaultMessage='Show this command in the autocomplete list'
+ />
+ </div>
+ </div>
+ </div>
+ {autocompleteFields}
+ <div className='backstage-form__footer'>
+ <FormError errors={[this.state.serverError, this.state.clientError]}/>
+ <Link
+ className='btn btn-sm'
+ to={'/settings/integrations/add'}
+ >
+ <FormattedMessage
+ id='add_command.cancel'
+ defaultMessage='Cancel'
+ />
+ </Link>
+ <SpinnerButton
+ className='btn btn-primary'
+ type='submit'
+ spinning={this.state.saving}
+ onClick={this.handleSubmit}
+ >
+ <FormattedMessage
+ id='add_command.save'
+ defaultMessage='Save'
+ />
+ </SpinnerButton>
+ </div>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/backstage/add_integration.jsx b/webapp/components/backstage/add_integration.jsx
index 5f4a69bfe..0ab36e101 100644
--- a/webapp/components/backstage/add_integration.jsx
+++ b/webapp/components/backstage/add_integration.jsx
@@ -56,6 +56,28 @@ export default class AddIntegration extends React.Component {
);
}
+ if (window.mm_config.EnableCommands === 'true') {
+ options.push(
+ <AddIntegrationOption
+ key='command'
+ image={WebhookIcon}
+ title={
+ <FormattedMessage
+ id='add_integration.command.title'
+ defaultMessage='Slash Command'
+ />
+ }
+ description={
+ <FormattedMessage
+ id='add_integration.command.description'
+ defaultMessage='Create slash commands to send events to external integrations and receive a response.'
+ />
+ }
+ link={'/settings/integrations/add/command'}
+ />
+ );
+ }
+
return (
<div className='backstage-content row'>
<div className='backstage-header'>
diff --git a/webapp/components/backstage/backstage_sidebar.jsx b/webapp/components/backstage/backstage_sidebar.jsx
index 13c4f8b50..172119b32 100644
--- a/webapp/components/backstage/backstage_sidebar.jsx
+++ b/webapp/components/backstage/backstage_sidebar.jsx
@@ -59,6 +59,15 @@ export default class BackstageSidebar extends React.Component {
/>
)}
/>
+ <BackstageSection
+ name='command'
+ title={(
+ <FormattedMessage
+ id='backstage_sidebar.integrations.add.command'
+ defaultMessage='Slash Command'
+ />
+ )}
+ />
</BackstageSection>
</BackstageCategory>
</ul>
diff --git a/webapp/components/backstage/installed_command.jsx b/webapp/components/backstage/installed_command.jsx
new file mode 100644
index 000000000..51adce160
--- /dev/null
+++ b/webapp/components/backstage/installed_command.jsx
@@ -0,0 +1,97 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import * as Utils from 'utils/utils.jsx';
+
+import {FormattedMessage} from 'react-intl';
+
+export default class InstalledCommand extends React.Component {
+ static get propTypes() {
+ return {
+ command: React.PropTypes.object.isRequired,
+ onRegenToken: React.PropTypes.func.isRequired,
+ onDelete: React.PropTypes.func.isRequired
+ };
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.handleRegenToken = this.handleRegenToken.bind(this);
+ this.handleDelete = this.handleDelete.bind(this);
+ }
+
+ handleRegenToken(e) {
+ e.preventDefault();
+
+ this.props.onRegenToken(this.props.command);
+ }
+
+ handleDelete(e) {
+ e.preventDefault();
+
+ this.props.onDelete(this.props.command);
+ }
+
+ render() {
+ const command = this.props.command;
+
+ return (
+ <div className='backstage-list__item'>
+ <div className='item-details'>
+ <div className='item-details__row'>
+ <span className='item-details__name'>
+ {command.display_name}
+ </span>
+ <span className='item-details__type'>
+ <FormattedMessage
+ id='installed_integrations.commandType'
+ defaultMessage='(Slash Command)'
+ />
+ </span>
+ </div>
+ <div className='item-details__row'>
+ <span className='item-details__description'>
+ {command.description}
+ </span>
+ </div>
+ <div className='item-details__row'>
+ <span className='item-details__creation'>
+ <FormattedMessage
+ id='installed_integrations.creation'
+ defaultMessage='Created by {creator} on {createAt, date, full}'
+ values={{
+ creator: Utils.displayUsername(command.creator_Id),
+ createAt: command.create_at
+ }}
+ />
+ </span>
+ </div>
+ </div>
+ <div className='item-actions'>
+ <a
+ href='#'
+ onClick={this.handleRegenToken}
+ >
+ <FormattedMessage
+ id='installed_integrations.regenToken'
+ defaultMessage='Regen Token'
+ />
+ </a>
+ {' - '}
+ <a
+ href='#'
+ onClick={this.handleDelete}
+ >
+ <FormattedMessage
+ id='installed_integrations.delete'
+ defaultMessage='Delete'
+ />
+ </a>
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/webapp/components/backstage/installed_incoming_webhook.jsx b/webapp/components/backstage/installed_incoming_webhook.jsx
index 95a303edc..cd9a6d761 100644
--- a/webapp/components/backstage/installed_incoming_webhook.jsx
+++ b/webapp/components/backstage/installed_incoming_webhook.jsx
@@ -12,20 +12,20 @@ export default class InstalledIncomingWebhook extends React.Component {
static get propTypes() {
return {
incomingWebhook: React.PropTypes.object.isRequired,
- onDeleteClick: React.PropTypes.func.isRequired
+ onDelete: React.PropTypes.func.isRequired
};
}
constructor(props) {
super(props);
- this.handleDeleteClick = this.handleDeleteClick.bind(this);
+ this.handleDelete = this.handleDelete.bind(this);
}
- handleDeleteClick(e) {
+ handleDelete(e) {
e.preventDefault();
- this.props.onDeleteClick(this.props.incomingWebhook);
+ this.props.onDelete(this.props.incomingWebhook);
}
render() {
@@ -69,7 +69,7 @@ export default class InstalledIncomingWebhook extends React.Component {
<div className='item-actions'>
<a
href='#'
- onClick={this.handleDeleteClick}
+ onClick={this.handleDelete}
>
<FormattedMessage
id='installed_integrations.delete'
diff --git a/webapp/components/backstage/installed_integrations.jsx b/webapp/components/backstage/installed_integrations.jsx
index fe84ae81a..e353b7f29 100644
--- a/webapp/components/backstage/installed_integrations.jsx
+++ b/webapp/components/backstage/installed_integrations.jsx
@@ -11,6 +11,7 @@ import * as Utils from 'utils/utils.jsx';
import {FormattedMessage} from 'react-intl';
import InstalledIncomingWebhook from './installed_incoming_webhook.jsx';
import InstalledOutgoingWebhook from './installed_outgoing_webhook.jsx';
+import InstalledCommand from './installed_command.jsx';
import {Link} from 'react-router';
export default class InstalledIntegrations extends React.Component {
@@ -24,10 +25,13 @@ export default class InstalledIntegrations extends React.Component {
this.deleteIncomingWebhook = this.deleteIncomingWebhook.bind(this);
this.regenOutgoingWebhookToken = this.regenOutgoingWebhookToken.bind(this);
this.deleteOutgoingWebhook = this.deleteOutgoingWebhook.bind(this);
+ this.regenCommandToken = this.regenCommandToken.bind(this);
+ this.deleteCommand = this.deleteCommand.bind(this);
this.state = {
incomingWebhooks: [],
outgoingWebhooks: [],
+ commands: [],
typeFilter: '',
filter: ''
};
@@ -55,6 +59,16 @@ export default class InstalledIntegrations extends React.Component {
AsyncClient.listOutgoingHooks();
}
}
+
+ if (window.mm_config.EnableCommands === 'true') {
+ if (IntegrationStore.hasReceivedCommands()) {
+ this.setState({
+ commands: IntegrationStore.getCommands()
+ });
+ } else {
+ AsyncClient.listTeamCommands();
+ }
+ }
}
componentWillUnmount() {
@@ -62,10 +76,24 @@ export default class InstalledIntegrations extends React.Component {
}
handleIntegrationChange() {
+ const incomingWebhooks = IntegrationStore.getIncomingWebhooks();
+ const outgoingWebhooks = IntegrationStore.getOutgoingWebhooks();
+ const commands = IntegrationStore.getCommands();
+
this.setState({
- incomingWebhooks: IntegrationStore.getIncomingWebhooks(),
- outgoingWebhooks: IntegrationStore.getOutgoingWebhooks()
+ incomingWebhooks,
+ outgoingWebhooks,
+ commands
});
+
+ // reset the type filter if we were viewing a category that is now empty
+ if ((this.state.typeFilter === 'incomingWebhooks' && incomingWebhooks.length === 0) ||
+ (this.state.typeFilter === 'outgoingWebhooks' && outgoingWebhooks.length === 0) ||
+ (this.state.typeFilter === 'commands' && commands.length === 0)) {
+ this.setState({
+ typeFilter: ''
+ });
+ }
}
updateTypeFilter(e, typeFilter) {
@@ -94,10 +122,18 @@ export default class InstalledIntegrations extends React.Component {
AsyncClient.deleteOutgoingHook(outgoingWebhook.id);
}
- renderTypeFilters(incomingWebhooks, outgoingWebhooks) {
+ regenCommandToken(command) {
+ AsyncClient.regenCommandToken(command.id);
+ }
+
+ deleteCommand(command) {
+ AsyncClient.deleteCommand(command.id);
+ }
+
+ renderTypeFilters(incomingWebhooks, outgoingWebhooks, commands) {
const fields = [];
- if (incomingWebhooks.length > 0 || outgoingWebhooks.length > 0) {
+ if (incomingWebhooks.length > 0 || outgoingWebhooks.length > 0 || commands.length > 0) {
let filterClassName = 'filter-sort';
if (this.state.typeFilter === '') {
filterClassName += ' filter-sort--active';
@@ -187,6 +223,39 @@ export default class InstalledIntegrations extends React.Component {
);
}
+ if (commands.length > 0) {
+ fields.push(
+ <span
+ key='commandsDivider'
+ className='divider'
+ >
+ {'|'}
+ </span>
+ );
+
+ let filterClassName = 'filter-sort';
+ if (this.state.typeFilter === 'commands') {
+ filterClassName += ' filter-sort--active';
+ }
+
+ fields.push(
+ <a
+ key='commandsFilter'
+ className={filterClassName}
+ href='#'
+ onClick={(e) => this.updateTypeFilter(e, 'commands')}
+ >
+ <FormattedMessage
+ id='installed_integrations.commandsFilter'
+ defaultMessage='Slash Commands ({count})'
+ values={{
+ count: commands.length
+ }}
+ />
+ </a>
+ );
+ }
+
return (
<div className='backstage-filters__sort'>
{fields}
@@ -197,7 +266,9 @@ export default class InstalledIntegrations extends React.Component {
render() {
const incomingWebhooks = this.state.incomingWebhooks;
const outgoingWebhooks = this.state.outgoingWebhooks;
+ const commands = this.state.commands;
+ // TODO description, name, creator filtering
const filter = this.state.filter.toLowerCase();
const integrations = [];
@@ -215,7 +286,7 @@ export default class InstalledIntegrations extends React.Component {
<InstalledIncomingWebhook
key={incomingWebhook.id}
incomingWebhook={incomingWebhook}
- onDeleteClick={this.deleteIncomingWebhook}
+ onDelete={this.deleteIncomingWebhook}
/>
);
}
@@ -242,6 +313,27 @@ export default class InstalledIntegrations extends React.Component {
}
}
+ if (!this.state.typeFilter || this.state.typeFilter === 'commands') {
+ for (const command of commands) {
+ if (filter) {
+ const channel = ChannelStore.get(command.channel_id);
+
+ if (!channel || channel.name.toLowerCase().indexOf(filter) === -1) {
+ continue;
+ }
+ }
+
+ integrations.push(
+ <InstalledCommand
+ key={command.id}
+ command={command}
+ onRegenToken={this.regenCommandToken}
+ onDelete={this.deleteCommand}
+ />
+ );
+ }
+ }
+
return (
<div className='backstage-content row'>
<div className='installed-integrations'>
@@ -270,7 +362,7 @@ export default class InstalledIntegrations extends React.Component {
</Link>
</div>
<div className='backstage-filters'>
- {this.renderTypeFilters(this.state.incomingWebhooks, this.state.outgoingWebhooks)}
+ {this.renderTypeFilters(incomingWebhooks, outgoingWebhooks, commands)}
<div className='backstage-filter__search'>
<i className='fa fa-search'></i>
<input
diff --git a/webapp/components/user_settings/manage_command_hooks.jsx b/webapp/components/user_settings/manage_command_hooks.jsx
deleted file mode 100644
index ce353ad64..000000000
--- a/webapp/components/user_settings/manage_command_hooks.jsx
+++ /dev/null
@@ -1,681 +0,0 @@
-// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import LoadingScreen from '../loading_screen.jsx';
-
-import * as Client from 'utils/client.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'react-intl';
-
-const holders = defineMessages({
- requestTypePost: {
- id: 'user.settings.cmds.request_type_post',
- defaultMessage: 'POST'
- },
- requestTypeGet: {
- id: 'user.settings.cmds.request_type_get',
- defaultMessage: 'GET'
- },
- addDisplayNamePlaceholder: {
- id: 'user.settings.cmds.add_display_name.placeholder',
- defaultMessage: 'Example: "Search patient records"'
- },
- addUsernamePlaceholder: {
- id: 'user.settings.cmds.add_username.placeholder',
- defaultMessage: 'Username'
- },
- addTriggerPlaceholder: {
- id: 'user.settings.cmds.add_trigger.placeholder',
- defaultMessage: 'Command trigger e.g. "hello" not including the slash'
- },
- addAutoCompleteDescPlaceholder: {
- id: 'user.settings.cmds.auto_complete_desc.placeholder',
- defaultMessage: 'Example: "Returns search results for patient records"'
- },
- addAutoCompleteHintPlaceholder: {
- id: 'user.settings.cmds.auto_complete_hint.placeholder',
- defaultMessage: 'Example: [Patient Name]'
- },
- adUrlPlaceholder: {
- id: 'user.settings.cmds.url.placeholder',
- defaultMessage: 'Must start with http:// or https://'
- },
- autocompleteYes: {
- id: 'user.settings.cmds.auto_complete.yes',
- defaultMessage: 'yes'
- },
- autocompleteNo: {
- id: 'user.settings.cmds.auto_complete.no',
- defaultMessage: 'no'
- }
-});
-
-import React from 'react';
-
-export default class ManageCommandCmds extends React.Component {
- constructor() {
- super();
-
- this.getCmds = this.getCmds.bind(this);
- this.addNewCmd = this.addNewCmd.bind(this);
- this.emptyCmd = this.emptyCmd.bind(this);
- this.updateTrigger = this.updateTrigger.bind(this);
- this.updateURL = this.updateURL.bind(this);
- this.updateMethod = this.updateMethod.bind(this);
- this.updateUsername = this.updateUsername.bind(this);
- this.updateIconURL = this.updateIconURL.bind(this);
- this.updateDisplayName = this.updateDisplayName.bind(this);
- this.updateAutoComplete = this.updateAutoComplete.bind(this);
- this.updateAutoCompleteDesc = this.updateAutoCompleteDesc.bind(this);
- this.updateAutoCompleteHint = this.updateAutoCompleteHint.bind(this);
-
- this.state = {cmds: [], cmd: this.emptyCmd(), getCmdsComplete: false};
- }
-
- static propTypes() {
- return {
- intl: intlShape.isRequired
- };
- }
-
- emptyCmd() {
- var cmd = {};
- cmd.url = '';
- cmd.trigger = '';
- cmd.method = 'P';
- cmd.username = '';
- cmd.icon_url = '';
- cmd.auto_complete = false;
- cmd.auto_complete_desc = '';
- cmd.auto_complete_hint = '';
- cmd.display_name = '';
- return cmd;
- }
-
- componentDidMount() {
- this.getCmds();
- }
-
- addNewCmd(e) {
- e.preventDefault();
-
- if (this.state.cmd.trigger === '' || this.state.cmd.url === '') {
- return;
- }
-
- var cmd = this.state.cmd;
- if (cmd.trigger.length !== 0) {
- cmd.trigger = cmd.trigger.trim();
- }
- cmd.url = cmd.url.trim();
-
- Client.addCommand(
- cmd,
- (data) => {
- let cmds = Object.assign([], this.state.cmds);
- if (!cmds) {
- cmds = [];
- }
- cmds.push(data);
- this.setState({cmds, addError: null, cmd: this.emptyCmd()});
- },
- (err) => {
- this.setState({addError: err.message});
- }
- );
- }
-
- removeCmd(id) {
- const data = {};
- data.id = id;
-
- Client.deleteCommand(
- data,
- () => {
- const cmds = this.state.cmds;
- let index = -1;
- for (let i = 0; i < cmds.length; i++) {
- if (cmds[i].id === id) {
- index = i;
- break;
- }
- }
-
- if (index !== -1) {
- cmds.splice(index, 1);
- }
-
- this.setState({cmds});
- },
- (err) => {
- this.setState({editError: err.message});
- }
- );
- }
-
- regenToken(id) {
- const regenData = {};
- regenData.id = id;
-
- Client.regenCommandToken(
- regenData,
- (data) => {
- const cmds = Object.assign([], this.state.cmds);
- for (let i = 0; i < cmds.length; i++) {
- if (cmds[i].id === id) {
- cmds[i] = data;
- break;
- }
- }
-
- this.setState({cmds, editError: null});
- },
- (err) => {
- this.setState({editError: err.message});
- }
- );
- }
-
- getCmds() {
- Client.listTeamCommands(
- (data) => {
- if (data) {
- this.setState({cmds: data, getCmdsComplete: true, editError: null});
- }
- },
- (err) => {
- this.setState({editError: err.message});
- }
- );
- }
-
- updateTrigger(e) {
- var cmd = this.state.cmd;
- cmd.trigger = e.target.value;
- this.setState(cmd);
- }
-
- updateURL(e) {
- var cmd = this.state.cmd;
- cmd.url = e.target.value;
- this.setState(cmd);
- }
-
- updateMethod(e) {
- var cmd = this.state.cmd;
- cmd.method = e.target.value;
- this.setState(cmd);
- }
-
- updateUsername(e) {
- var cmd = this.state.cmd;
- cmd.username = e.target.value;
- this.setState(cmd);
- }
-
- updateIconURL(e) {
- var cmd = this.state.cmd;
- cmd.icon_url = e.target.value;
- this.setState(cmd);
- }
-
- updateDisplayName(e) {
- var cmd = this.state.cmd;
- cmd.display_name = e.target.value;
- this.setState(cmd);
- }
-
- updateAutoComplete(e) {
- var cmd = this.state.cmd;
- cmd.auto_complete = e.target.checked;
- this.setState(cmd);
- }
-
- updateAutoCompleteDesc(e) {
- var cmd = this.state.cmd;
- cmd.auto_complete_desc = e.target.value;
- this.setState(cmd);
- }
-
- updateAutoCompleteHint(e) {
- var cmd = this.state.cmd;
- cmd.auto_complete_hint = e.target.value;
- this.setState(cmd);
- }
-
- render() {
- let addError;
- if (this.state.addError) {
- addError = <label className='has-error'>{this.state.addError}</label>;
- }
-
- let editError;
- if (this.state.editError) {
- addError = <label className='has-error'>{this.state.editError}</label>;
- }
-
- const cmds = [];
- this.state.cmds.forEach((cmd) => {
- let triggerDiv;
- if (cmd.trigger && cmd.trigger.length !== 0) {
- triggerDiv = (
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.trigger'
- defaultMessage='Command Trigger Word: '
- />
- </strong>{cmd.trigger}
- </div>
- );
- }
-
- cmds.push(
- <div
- key={cmd.id}
- className='webhook__item webcmd__item'
- >
- {triggerDiv}
- <div className='padding-top x2 webcmd__url'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.url'
- defaultMessage='Request URL: '
- />
- </strong><span className='word-break--all'>{cmd.url}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.request_type'
- defaultMessage='Request Method: '
- />
- </strong>
- <span className='word-break--all'>
- {
- cmd.method === 'P' ?
- <FormattedMessage
- id='user.settings.cmds.request_type_post'
- defaultMessage='POST'
- /> :
- <FormattedMessage
- id='user.settings.cmds.request_type_get'
- defaultMessage='GET'
- />
- }
- </span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.username'
- defaultMessage='Response Username: '
- />
- </strong><span className='word-break--all'>{cmd.username}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.icon_url'
- defaultMessage='Response Icon: '
- />
- </strong><span className='word-break--all'>{cmd.icon_url}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.auto_complete'
- defaultMessage='Autocomplete: '
- />
- </strong><span className='word-break--all'>{cmd.auto_complete ? this.props.intl.formatMessage(holders.autocompleteYes) : this.props.intl.formatMessage(holders.autocompleteNo)}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_hint'
- defaultMessage='Autocomplete Hint: '
- />
- </strong><span className='word-break--all'>{cmd.auto_complete_hint}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_desc'
- defaultMessage='Autocomplete Description: '
- />
- </strong><span className='word-break--all'>{cmd.auto_complete_desc}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.display_name'
- defaultMessage='Descriptive Label: '
- />
- </strong><span className='word-break--all'>{cmd.display_name}</span>
- </div>
- <div className='padding-top'>
- <strong>
- <FormattedMessage
- id='user.settings.cmds.token'
- defaultMessage='Token: '
- />
- </strong>{cmd.token}
- </div>
- <div className='padding-top'>
- <a
- className='text-danger'
- href='#'
- onClick={this.regenToken.bind(this, cmd.id)}
- >
- <FormattedMessage
- id='user.settings.cmds.regen'
- defaultMessage='Regen Token'
- />
- </a>
- <a
- className='webhook__remove webcmd__remove'
- href='#'
- onClick={this.removeCmd.bind(this, cmd.id)}
- >
- <span aria-hidden='true'>{'×'}</span>
- </a>
- </div>
- <div className='padding-top x2 divider-light'></div>
- </div>
- );
- });
-
- let displayCmds;
- if (!this.state.getCmdsComplete) {
- displayCmds = <LoadingScreen/>;
- } else if (cmds.length > 0) {
- displayCmds = cmds;
- } else {
- displayCmds = (
- <div className='padding-top x2'>
- <FormattedMessage
- id='user.settings.cmds.none'
- defaultMessage='None'
- />
- </div>
- );
- }
-
- const existingCmds = (
- <div className='webhooks__container webcmds__container'>
- <label className='control-label padding-top x2'>
- <FormattedMessage
- id='user.settings.cmds.existing'
- defaultMessage='Existing commands'
- />
- </label>
- <div className='padding-top divider-light'></div>
- <div className='webhooks__list webcmds__list'>
- {displayCmds}
- </div>
- </div>
- );
-
- const disableButton = this.state.cmd.trigger === '' || this.state.cmd.url === '';
-
- return (
- <div key='addCommandCmd'>
- <FormattedHTMLMessage
- id='user.settings.cmds.add_desc'
- defaultMessage='Create slash commands to send events to external integrations and receive a response. For example typing `/patient Joe Smith` could bring back search results from your internal health records management system for the name “Joe Smith”. Please see <a href="http://docs.mattermost.com/developer/slash-commands.html">Slash commands documentation</a> for detailed instructions. View all slash commands configured on this team below.'
- />
- <div><label className='control-label padding-top x2'>
- <FormattedMessage
- id='user.settings.cmds.add_new'
- defaultMessage='Add a new command'
- />
- </label></div>
- <div className='padding-top divider-light'></div>
- <div className='padding-top'>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.trigger'
- defaultMessage='Command Trigger Word: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='trigger'
- className='form-control'
- value={this.state.cmd.trigger}
- onChange={this.updateTrigger}
- placeholder={this.props.intl.formatMessage(holders.addTriggerPlaceholder)}
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.trigger_desc'
- defaultMessage='Examples: /patient, /client, /employee Reserved: /echo, /join, /logout, /me, /shrug'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.url'
- defaultMessage='Request URL: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='URL'
- className='form-control'
- value={this.state.cmd.url}
- rows={1}
- onChange={this.updateURL}
- placeholder={this.props.intl.formatMessage(holders.adUrlPlaceholder)}
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.url_desc'
- defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.request_type'
- defaultMessage='Request Method: '
- />
- </label>
- <div className='padding-top'>
- <select
- ref='method'
- className='form-control'
- value={this.state.cmd.method}
- onChange={this.updateMethod}
- >
- <option value='P'>
- {this.props.intl.formatMessage(holders.requestTypePost)}
- </option>
- <option value='G'>
- {this.props.intl.formatMessage(holders.requestTypeGet)}
- </option>
- </select>
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.request_type_desc'
- defaultMessage='The type of command request issued to the Request URL.'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.username'
- defaultMessage='Response Username: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='username'
- className='form-control'
- value={this.state.cmd.username}
- onChange={this.updateUsername}
- placeholder={this.props.intl.formatMessage(holders.addUsernamePlaceholder)}
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.username_desc'
- defaultMessage='Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols "-", "_", and "." .'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.icon_url'
- defaultMessage='Response Icon: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='iconURL'
- className='form-control'
- value={this.state.cmd.icon_url}
- onChange={this.updateIconURL}
- placeholder='https://www.example.com/myicon.png'
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.icon_url_desc'
- defaultMessage='Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.auto_complete'
- defaultMessage='Autocomplete: '
- />
- </label>
- <div className='padding-top'>
- <div className='checkbox'>
- <label>
- <input
- type='checkbox'
- checked={this.state.cmd.auto_complete}
- onChange={this.updateAutoComplete}
- />
- <FormattedMessage
- id='user.settings.cmds.auto_complete_help'
- defaultMessage=' Show this command in the autocomplete list.'
- />
- </label>
- </div>
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_hint'
- defaultMessage='Autocomplete Hint: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='autoCompleteHint'
- className='form-control'
- value={this.state.cmd.auto_complete_hint}
- onChange={this.updateAutoCompleteHint}
- placeholder={this.props.intl.formatMessage(holders.addAutoCompleteHintPlaceholder)}
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_hint_desc'
- defaultMessage='Optional hint in the autocomplete list about parameters needed for command.'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_desc'
- defaultMessage='Autocomplete Description: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='autoCompleteDesc'
- className='form-control'
- value={this.state.cmd.auto_complete_desc}
- onChange={this.updateAutoCompleteDesc}
- placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)}
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_desc_desc'
- defaultMessage='Optional short description of slash command for the autocomplete list.'
- />
- </div>
- </div>
-
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.display_name'
- defaultMessage='Descriptive Label: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='displayName'
- className='form-control'
- value={this.state.cmd.display_name}
- onChange={this.updateDisplayName}
- placeholder={this.props.intl.formatMessage(holders.addDisplayNamePlaceholder)}
- />
- </div>
- <div className='padding-top'>
- <FormattedMessage
- id='user.settings.cmds.cmd_display_name'
- defaultMessage='Brief description of slash command to show in listings.'
- />
- </div>
- {addError}
- </div>
-
- <div className='padding-top x2 padding-bottom'>
- <a
- className={'btn btn-sm btn-primary'}
- href='#'
- disabled={disableButton}
- onClick={this.addNewCmd}
- >
- <FormattedMessage
- id='user.settings.cmds.add'
- defaultMessage='Add'
- />
- </a>
- </div>
- </div>
- {existingCmds}
- {editError}
- </div>
- );
- }
-}
-
-export default injectIntl(ManageCommandCmds);
diff --git a/webapp/components/user_settings/user_settings.jsx b/webapp/components/user_settings/user_settings.jsx
index 904232da9..d89298cfb 100644
--- a/webapp/components/user_settings/user_settings.jsx
+++ b/webapp/components/user_settings/user_settings.jsx
@@ -7,7 +7,6 @@ import NotificationsTab from './user_settings_notifications.jsx';
import SecurityTab from './user_settings_security.jsx';
import GeneralTab from './user_settings_general.jsx';
import DeveloperTab from './user_settings_developer.jsx';
-import IntegrationsTab from './user_settings_integrations.jsx';
import DisplayTab from './user_settings_display.jsx';
import AdvancedTab from './user_settings_advanced.jsx';
@@ -98,20 +97,6 @@ export default class UserSettings extends React.Component {
/>
</div>
);
- } else if (this.props.activeTab === 'integrations') {
- return (
- <div>
- <IntegrationsTab
- ref='activeTab'
- user={this.state.user}
- activeSection={this.props.activeSection}
- updateSection={this.props.updateSection}
- updateTab={this.props.updateTab}
- closeModal={this.props.closeModal}
- collapseModal={this.props.collapseModal}
- />
- </div>
- );
} else if (this.props.activeTab === 'display') {
return (
<div>
diff --git a/webapp/components/user_settings/user_settings_integrations.jsx b/webapp/components/user_settings/user_settings_integrations.jsx
deleted file mode 100644
index 37081b863..000000000
--- a/webapp/components/user_settings/user_settings_integrations.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import SettingItemMin from '../setting_item_min.jsx';
-import SettingItemMax from '../setting_item_max.jsx';
-import ManageCommandHooks from './manage_command_hooks.jsx';
-
-import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'react-intl';
-
-const holders = defineMessages({
- cmdName: {
- id: 'user.settings.integrations.commands',
- defaultMessage: 'Slash Commands'
- },
- cmdDesc: {
- id: 'user.settings.integrations.commandsDescription',
- defaultMessage: 'Manage your slash commands'
- }
-});
-
-import React from 'react';
-
-class UserSettingsIntegrationsTab extends React.Component {
- constructor(props) {
- super(props);
-
- this.updateSection = this.updateSection.bind(this);
-
- this.state = {};
- }
- updateSection(section) {
- $('.settings-modal .modal-body').scrollTop(0).perfectScrollbar('update');
- this.props.updateSection(section);
- }
- render() {
- let commandHooksSection;
- var inputs = [];
- const {formatMessage} = this.props.intl;
-
- if (global.window.mm_config.EnableCommands === 'true') {
- if (this.props.activeSection === 'command-hooks') {
- inputs.push(
- <ManageCommandHooks key='command-hook-ui'/>
- );
-
- commandHooksSection = (
- <SettingItemMax
- title={formatMessage(holders.cmdName)}
- width='medium'
- inputs={inputs}
- updateSection={(e) => {
- this.updateSection('');
- e.preventDefault();
- }}
- />
- );
- } else {
- commandHooksSection = (
- <SettingItemMin
- title={formatMessage(holders.cmdName)}
- width='medium'
- describe={formatMessage(holders.cmdDesc)}
- updateSection={() => {
- this.updateSection('command-hooks');
- }}
- />
- );
- }
- }
-
- return (
- <div>
- <div className='modal-header'>
- <button
- type='button'
- className='close'
- data-dismiss='modal'
- aria-label='Close'
- onClick={this.props.closeModal}
- >
- <span aria-hidden='true'>{'×'}</span>
- </button>
- <h4
- className='modal-title'
- ref='title'
- >
- <div className='modal-back'>
- <i
- className='fa fa-angle-left'
- onClick={this.props.collapseModal}
- />
- </div>
- <FormattedMessage
- id='user.settings.integrations.title'
- defaultMessage='Integration Settings'
- />
- </h4>
- </div>
- <div className='user-settings'>
- <h3 className='tab-header'>
- <FormattedMessage
- id='user.settings.integrations.title'
- defaultMessage='Integration Settings'
- />
- </h3>
- <div className='divider-dark first'/>
- {commandHooksSection}
- <div className='divider-dark'/>
- </div>
- </div>
- );
- }
-}
-
-UserSettingsIntegrationsTab.propTypes = {
- intl: intlShape.isRequired,
- user: React.PropTypes.object,
- updateSection: React.PropTypes.func,
- updateTab: React.PropTypes.func,
- activeSection: React.PropTypes.string,
- closeModal: React.PropTypes.func.isRequired,
- collapseModal: React.PropTypes.func.isRequired
-};
-
-export default injectIntl(UserSettingsIntegrationsTab);
diff --git a/webapp/components/user_settings/user_settings_modal.jsx b/webapp/components/user_settings/user_settings_modal.jsx
index b71547baf..43fb728bd 100644
--- a/webapp/components/user_settings/user_settings_modal.jsx
+++ b/webapp/components/user_settings/user_settings_modal.jsx
@@ -31,10 +31,6 @@ const holders = defineMessages({
id: 'user.settings.modal.developer',
defaultMessage: 'Developer'
},
- integrations: {
- id: 'user.settings.modal.integrations',
- defaultMessage: 'Integrations'
- },
display: {
id: 'user.settings.modal.display',
defaultMessage: 'Display'
@@ -227,7 +223,6 @@ class UserSettingsModal extends React.Component {
if (this.state.currentUser == null) {
return (<div/>);
}
- var isAdmin = Utils.isAdmin(this.state.currentUser.roles);
var tabs = [];
tabs.push({name: 'general', uiName: formatMessage(holders.general), icon: 'glyphicon glyphicon-cog'});
@@ -237,18 +232,6 @@ class UserSettingsModal extends React.Component {
tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'});
}
- if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true' || global.window.mm_config.EnableCommands === 'true') {
- var show = global.window.mm_config.EnableOnlyAdminIntegrations !== 'true';
-
- if (global.window.mm_config.EnableOnlyAdminIntegrations === 'true' && isAdmin) {
- show = true;
- }
-
- if (show) {
- tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
- }
- }
-
tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'glyphicon glyphicon-eye-open'});
tabs.push({name: 'advanced', uiName: formatMessage(holders.advanced), icon: 'glyphicon glyphicon-list-alt'});
diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json
index f17ed5817..b66b079e7 100644
--- a/webapp/i18n/en.json
+++ b/webapp/i18n/en.json
@@ -27,6 +27,36 @@
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android Native App",
"activity_log_modal.iphoneNativeApp": "iPhone Native App",
+ "add_command.autocomplete": "Autocomplete",
+ "add_command.autocomplete.help": " Show this command in the autocomplete list.",
+ "add_command.autocompleteDescription": "Autocomplete Description",
+ "add_command.autocompleteDescription.help": "Optional short description of slash command for the autocomplete list.",
+ "add_command.autocompleteDescription.placeholder": "Example: \"Returns search results for patient records\"",
+ "add_command.autocompleteHint": "Autocomplete Hint",
+ "add_command.autocompleteHint.help": "Optional hint in the autocomplete list about parameters needed for command.",
+ "add_command.autocompleteHint.placeholder": "Example: [Patient Name]",
+ "add_command.description": "Description",
+ "add_command.displayName": "Display Name",
+ "add_command.header": "Add Slash Command",
+ "add_command.iconUrl": "Response Icon",
+ "add_command.iconUrl.placeholder": "https://www.example.com/myicon.png",
+ "add_command.iconUrl.help": "Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.",
+ "add_command.method": "Request Method",
+ "add_command.method.get": "GET",
+ "add_command.method.help": "The type of command request issued to the Request URL.",
+ "add_command.method.post": "POST",
+ "add_command.trigger": "Command Trigger Word",
+ "add_command.trigger.help1": "Examples: /patient, /client, /employee",
+ "add_command.trigger.help2": "Reserved: /echo, /join, /logout, /me, /shrug",
+ "add_command.trigger.placeholder": "Command trigger e.g. \"hello\" not including the slash",
+ "add_command.triggerRequired": "A trigger word is required",
+ "add_command.username": "Response Username",
+ "add_command.username.help": "Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols \"-\", \"_\", and \".\" .",
+ "add_command.username.placeholder": "Username",
+ "add_command.url": "Request URL",
+ "add_command.url.help": "The callback URL to receive the HTTP POST or GET event request when the slash command is run.",
+ "add_command.url.placeholder": "Must start with http:// or https://",
+ "add_command.urlRequired": "A request URL is required",
"add_incoming_webhook.cancel": "Cancel",
"add_incoming_webhook.channel": "Channel",
"add_incoming_webhook.channelRequired": "A valid channel is required",
@@ -35,6 +65,8 @@
"add_incoming_webhook.name": "Name",
"add_incoming_webhook.save": "Save",
"add_integration.header": "Add Integration",
+ "add_integration.command.description": "Create slash commands to send events to external integrations and receive a response.",
+ "add_integration.command.title": "Slash Command",
"add_integration.incomingWebhook.description": "Create webhook URLs for use in external integrations.",
"add_integration.incomingWebhook.title": "Incoming Webhook",
"add_integration.outgoingWebhook.description": "Create webhooks to send new message events to an external integration.",
@@ -588,6 +620,7 @@
"backstage_navbar.backToMattermost": "Back to {siteName}",
"backstage_sidebar.integrations": "Integrations",
"backstage_sidebar.integrations.add": "Add Integration",
+ "backstage_sidebar.integrations.add.command": "Outgoing Webhook",
"backstage_sidebar.integrations.add.incomingWebhook": "Incoming Webhook",
"backstage_sidebar.integrations.add.outgoingWebhook": "Outgoing Webhook",
"backstage_sidebar.integrations.installed": "Installed Integrations",
@@ -817,6 +850,8 @@
"installed_integrations.creation": "Created by {creator} on {createAt, date, full}",
"installed_integrations.delete": "Delete",
"installed_integrations.header": "Installed Integrations",
+ "installed_integrations.commandType": "(Slash Command)",
+ "installed_integrations.commandsFilter": "Slash Commands ({count})",
"installed_integrations.incomingWebhookType": "(Incoming Webhook)",
"installed_integrations.incomingWebhooksFilter": "Incoming Webhooks ({count})",
"installed_integrations.outgoingWebhookType": "(Outgoing Webhook)",
@@ -1222,42 +1257,6 @@
"user.settings.advance.sendTitle": "Send messages on Ctrl + Enter",
"user.settings.advance.slashCmd_autocmp": "Enable external application to offer slash command autocomplete",
"user.settings.advance.title": "Advanced Settings",
- "user.settings.cmds.add": "Add",
- "user.settings.cmds.add_desc": "Create slash commands to send events to external integrations and receive a response. For example typing `/patient Joe Smith` could bring back search results from your internal health records management system for the name “Joe Smith”. Please see <a href=\"http://docs.mattermost.com/developer/slash-commands.html\">Slash commands documentation</a> for detailed instructions. View all slash commands configured on this team below.",
- "user.settings.cmds.add_display_name.placeholder": "Example: \"Search patient records\"",
- "user.settings.cmds.add_new": "Add a new command",
- "user.settings.cmds.add_trigger.placeholder": "Command trigger e.g. \"hello\" not including the slash",
- "user.settings.cmds.add_username.placeholder": "Username",
- "user.settings.cmds.auto_complete": "Autocomplete: ",
- "user.settings.cmds.auto_complete.no": "no",
- "user.settings.cmds.auto_complete.yes": "yes",
- "user.settings.cmds.auto_complete_desc": "Autocomplete Description: ",
- "user.settings.cmds.auto_complete_desc.placeholder": "Example: \"Returns search results for patient records\"",
- "user.settings.cmds.auto_complete_desc_desc": "Optional short description of slash command for the autocomplete list.",
- "user.settings.cmds.auto_complete_help": " Show this command in the autocomplete list.",
- "user.settings.cmds.auto_complete_hint": "Autocomplete Hint: ",
- "user.settings.cmds.auto_complete_hint.placeholder": "Example: [Patient Name]",
- "user.settings.cmds.auto_complete_hint_desc": "Optional hint in the autocomplete list about parameters needed for command.",
- "user.settings.cmds.cmd_display_name": "Brief description of slash command to show in listings.",
- "user.settings.cmds.display_name": "Descriptive Label: ",
- "user.settings.cmds.existing": "Existing commands",
- "user.settings.cmds.icon_url": "Response Icon: ",
- "user.settings.cmds.icon_url_desc": "Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.",
- "user.settings.cmds.none": "None",
- "user.settings.cmds.regen": "Regen Token",
- "user.settings.cmds.request_type": "Request Method: ",
- "user.settings.cmds.request_type_desc": "The type of command request issued to the Request URL.",
- "user.settings.cmds.request_type_get": "GET",
- "user.settings.cmds.request_type_post": "POST",
- "user.settings.cmds.slashCmd_autocmp": "Enable external application to offer autocomplete",
- "user.settings.cmds.token": "Token: ",
- "user.settings.cmds.trigger": "Command Trigger Word: ",
- "user.settings.cmds.trigger_desc": "Examples: /patient, /client, /employee Reserved: /echo, /join, /logout, /me, /shrug",
- "user.settings.cmds.url": "Request URL: ",
- "user.settings.cmds.url.placeholder": "Must start with http:// or https://",
- "user.settings.cmds.url_desc": "The callback URL to receive the HTTP POST or GET event request when the slash command is run.",
- "user.settings.cmds.username": "Response Username: ",
- "user.settings.cmds.username_desc": "Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols \"-\", \"_\", and \".\" .",
"user.settings.custom_theme.awayIndicator": "Away Indicator",
"user.settings.custom_theme.buttonBg": "Button BG",
"user.settings.custom_theme.buttonColor": "Button Text",
@@ -1344,9 +1343,6 @@
"user.settings.import_theme.importHeader": "Import Slack Theme",
"user.settings.import_theme.submit": "Submit",
"user.settings.import_theme.submitError": "Invalid format, please try copying and pasting in again.",
- "user.settings.integrations.commands": "Slash Commands",
- "user.settings.integrations.commandsDescription": "Manage your slash commands",
- "user.settings.integrations.title": "Integration Settings",
"user.settings.languages.change": "Change interface language",
"user.settings.mfa.add": "Add MFA to your account",
"user.settings.mfa.addHelp": "To add multi-factor authentication to your account you must have a smartphone with Google Authenticator installed.",
@@ -1362,7 +1358,6 @@
"user.settings.modal.developer": "Developer",
"user.settings.modal.display": "Display",
"user.settings.modal.general": "General",
- "user.settings.modal.integrations": "Integrations",
"user.settings.modal.notifications": "Notifications",
"user.settings.modal.security": "Security",
"user.settings.modal.title": "Account Settings",
diff --git a/webapp/i18n/es.json b/webapp/i18n/es.json
index 903479e0f..ab44f1ea1 100644
--- a/webapp/i18n/es.json
+++ b/webapp/i18n/es.json
@@ -27,6 +27,29 @@
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Android App Nativa",
"activity_log_modal.iphoneNativeApp": "iPhone App Nativa",
+ "add_command.autocomplete.help": "Mostrar este comando en la lista de auto completado.",
+ "add_command.autocompleteDescription": "Descripción del Autocompletado",
+ "add_command.autocompleteDescription.help": "Descripción corta opcional para la lista de autocompletado del comando de barra.",
+ "add_command.autocompleteDescription.placeholder": "Ejemplo: \"Retorna resultados de una búsqueda con los registros de un paciente\"",
+ "add_command.autocompleteHint": "Pista del Autocompletado",
+ "add_command.autocompleteHint.help": "Pista opcional que aparece como paramentros necesarios en la lista de autocompletado para el comando.",
+ "add_command.autocompleteHint.placeholder": "Ejemplo: [Nombre del Paciente]",
+ "add_command.iconUrl": "Icono de Respuesta",
+ "add_command.iconUrl.help": "Escoge una imagen de perfil que reemplazara los mensajes publicados por este comando de barra. Ingresa el URL de un archivo .png o .jpg de al menos 128 x 128 pixels.",
+ "add_command.method": "Método de Solicitud",
+ "add_command.method.get": "GET",
+ "add_command.method.help": "El tipo de comando que se utiliza al hacer una solicitud al URL.",
+ "add_command.method.post": "POST",
+ "add_command.trigger": "Palabra Gatilladora del Comando",
+ "add_command.trigger.help1": "Ejemplos: /paciente, /cliente, /empleado",
+ "add_command.trigger.help2": "Reservadas: /echo, /join, /logout, /me, /shrug",
+ "add_command.trigger.placeholder": "Gatillador del Comando ej. \"hola\" no se debe incluir la barra",
+ "add_command.url": "URL de Solicitud",
+ "add_command.url.help": "El URL para recibir el evento de la solicitud HTTP POST o GET cuando se ejecuta el comando de barra.",
+ "add_command.url.placeholder": "Debe comenzar con http:// o https://",
+ "add_command.username": "Nombre de usuario de Respuesta",
+ "add_command.username.help": "Escoge un nombre de usuario que reemplazara los mensajes publicados por este comando de barra. Los nombres de usuario pueden tener hasta 22 caracteres y contener letras en minúsculas, números y los siguientes símbolos \"-\", \"_\", y \".\" .",
+ "add_command.username.placeholder": "Nombre de usuario",
"add_incoming_webhook.cancel": "Cancelar",
"add_incoming_webhook.channel": "Canal",
"add_incoming_webhook.channelRequired": "Es obligatorio asignar un canal válido",
@@ -1222,42 +1245,6 @@
"user.settings.advance.sendTitle": "Enviar mensajes con Ctrl + Retorno",
"user.settings.advance.slashCmd_autocmp": "Habilitar que una aplicación externa ofrezca el autocompletado de los comandos de barra",
"user.settings.advance.title": "Configuración Avanzada",
- "user.settings.cmds.add": "Agregar",
- "user.settings.cmds.add_desc": "Crea comandos de barra para enviar eventos a integraciones externas recibiendo una respuesta. Por ejemplo al escribir `/paciente Joe Smith` podría retornar los resultados de una búsqueda de los regístros de salud en tu sistema de administración para el nombre “Joe Smith”. Revisa la <a href=\"http://docs.mattermost.com/developer/slash-commands.html\">documentación de Comandos de Barra</a> para instrucciones detalladas. Ver todos los comandos de barra configurados para este equipo en la parte de abajo.",
- "user.settings.cmds.add_display_name.placeholder": "Ejemplo: \"Buscar registros del paciente\"",
- "user.settings.cmds.add_new": "Agregar un nuevo comando",
- "user.settings.cmds.add_trigger.placeholder": "Gatillador del Comando ej. \"hola\" no se debe incluir la barra",
- "user.settings.cmds.add_username.placeholder": "Nombre de usuario",
- "user.settings.cmds.auto_complete": "Autocompletado: ",
- "user.settings.cmds.auto_complete.no": "no",
- "user.settings.cmds.auto_complete.yes": "sí",
- "user.settings.cmds.auto_complete_desc": "Descripción del Autocompletado: ",
- "user.settings.cmds.auto_complete_desc.placeholder": "Ejemplo: \"Retorna resultados de una búsqueda con los registros de un paciente\"",
- "user.settings.cmds.auto_complete_desc_desc": "Descripción corta opcional para la lista de autocompletado del comando de barra.",
- "user.settings.cmds.auto_complete_help": "Mostrar este comando en la lista de auto completado.",
- "user.settings.cmds.auto_complete_hint": "Pista del Autocompletado: ",
- "user.settings.cmds.auto_complete_hint.placeholder": "Ejemplo: [Nombre del Paciente]",
- "user.settings.cmds.auto_complete_hint_desc": "Pista opcional que aparece como paramentros necesarios en la lista de autocompletado para el comando.",
- "user.settings.cmds.cmd_display_name": "Breve descripción del comando de barra para mostrar en el listado.",
- "user.settings.cmds.display_name": "Etiqueta Descriptiva: ",
- "user.settings.cmds.existing": "Comandos existentes",
- "user.settings.cmds.icon_url": "Icono de Respuesta: ",
- "user.settings.cmds.icon_url_desc": "Escoge una imagen de perfil que reemplazara los mensajes publicados por este comando de barra. Ingresa el URL de un archivo .png o .jpg de al menos 128 x 128 pixels.",
- "user.settings.cmds.none": "Ninguno",
- "user.settings.cmds.regen": "Regenerar Token",
- "user.settings.cmds.request_type": "Método de Solicitud: ",
- "user.settings.cmds.request_type_desc": "El tipo de comando que se utiliza al hacer una solicitud al URL.",
- "user.settings.cmds.request_type_get": "GET",
- "user.settings.cmds.request_type_post": "POST",
- "user.settings.cmds.slashCmd_autocmp": "Habilitar que una aplicación externa ofrezca autocompletado",
- "user.settings.cmds.token": "Token: ",
- "user.settings.cmds.trigger": "Palabra Gatilladora del Comando: ",
- "user.settings.cmds.trigger_desc": "Ejemplos: /paciente, /cliente, /empleado Reservadas: /echo, /join, /logout, /me, /shrug",
- "user.settings.cmds.url": "URL de Solicitud: ",
- "user.settings.cmds.url.placeholder": "Debe comenzar con http:// o https://",
- "user.settings.cmds.url_desc": "El URL para recibir el evento de la solicitud HTTP POST o GET cuando se ejecuta el comando de barra.",
- "user.settings.cmds.username": "Nombre de usuario de Respuesta: ",
- "user.settings.cmds.username_desc": "Escoge un nombre de usuario que reemplazara los mensajes publicados por este comando de barra. Los nombres de usuario pueden tener hasta 22 caracteres y contener letras en minúsculas, números y los siguientes símbolos \"-\", \"_\", y \".\" .",
"user.settings.custom_theme.awayIndicator": "Indicador Ausente",
"user.settings.custom_theme.buttonBg": "Fondo Botón",
"user.settings.custom_theme.buttonColor": "Texto Botón",
@@ -1344,9 +1331,6 @@
"user.settings.import_theme.importHeader": "Importar Tema de Slack",
"user.settings.import_theme.submit": "Enviar",
"user.settings.import_theme.submitError": "Formato inválido, por favor intenta copiando y pegando nuevamente.",
- "user.settings.integrations.commands": "Comandos de Barra",
- "user.settings.integrations.commandsDescription": "Administra tus comandos de barra",
- "user.settings.integrations.title": "Configuraciones de Integración",
"user.settings.languages.change": "Cambia el idioma con el que se muestra la intefaz de usuario",
"user.settings.mfa.add": "Agrega AMF a tu cuenta",
"user.settings.mfa.addHelp": "Para agregar autenticación de múltiples factores a tu cuenta debes tener un teléfono inteligente con Google Authenticator instalado.",
@@ -1362,7 +1346,6 @@
"user.settings.modal.developer": "Desarrollo",
"user.settings.modal.display": "Visualización",
"user.settings.modal.general": "General",
- "user.settings.modal.integrations": "Integraciones",
"user.settings.modal.notifications": "Notificaciones",
"user.settings.modal.security": "Seguridad",
"user.settings.modal.title": "Configuración de la Cuenta",
diff --git a/webapp/i18n/fr.json b/webapp/i18n/fr.json
index 4bcc9e040..ffbe89130 100644
--- a/webapp/i18n/fr.json
+++ b/webapp/i18n/fr.json
@@ -22,6 +22,28 @@
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "Application Android",
"activity_log_modal.iphoneNativeApp": "Application pour iPhone",
+ "add_command.autocomplete.help": "Afficher cette commande dans la liste d'auto-complétion",
+ "add_command.autocompleteDescription": "Description de l'auto-complétion",
+ "add_command.autocompleteDescription.help": "Description facultative de la commande slash dans la la liste d'auto-complétion.",
+ "add_command.autocompleteDescription.placeholder": "Exemple : \"Retourne les résultats de recherche de dossiers médicaux\"",
+ "add_command.autocompleteHint": "Explication pour l'auto-complétion",
+ "add_command.autocompleteHint.help": "Explication facultative pour la liste d'auto-complétion au sujet des paramètres requis par cette commande slash.",
+ "add_command.autocompleteHint.placeholder": "Exemple : [Nom du patient]",
+ "add_command.iconUrl": "Icône de la réponse",
+ "add_command.iconUrl.help": "Choisissez une photo de profil pour les réponses à cette commande slash. Entrez l'URL d'un fichier .png ou .jpg d'au moins 128x128 pixels.",
+ "add_command.method": "Méthode de requête",
+ "add_command.method.get": "GET",
+ "add_command.method.help": "Le type de méthode de requête HTTP envoyé à cette URL.",
+ "add_command.method.post": "POST",
+ "add_command.token": "Jeton",
+ "add_command.trigger": "Mot-clé de déclenchement",
+ "add_command.trigger.help1": "Exemples: /patient, /client, /employé",
+ "add_command.trigger.help2": "Mots réservés : /echo, /join, /logout, /me, /shrug",
+ "add_command.url": "URL de requête",
+ "add_command.url.help": "L'URL de callback qui recevra la requête POST ou GET quand cette commande slash est exécutée.",
+ "add_command.url.placeholder": "Doit commencer par http:// ou https://",
+ "add_command.username": "Utilisateur affiché dans la réponse",
+ "add_command.username.help": "Choisissez un nom d'utilisateur qui sera affiché dans la réponse de la commande slash. Les noms d'utilisateurs peuvent contenir jusqu'à 22 caractères, chiffres, lettres minuscules et symboles \"-\", \"_\" et \".\".",
"admin.audits.reload": "Rafraîchir",
"admin.audits.title": "Activité de l'utilisateur",
"admin.compliance.directoryDescription": "Répertoire des rapports de conformité. Si non spécifié : ./data/ .",
@@ -1161,42 +1183,6 @@
"user.settings.advance.sendTitle": "Envoyer vos messages avec Ctrl+Entrée",
"user.settings.advance.slashCmd_autocmp": "Autoriser les applications externes à propose l'auto-complétion",
"user.settings.advance.title": "Paramètres avancés",
- "user.settings.cmds.add": "Ajouter",
- "user.settings.cmds.add_desc": "Créez des commandes slash pour envoyer des événements à des intégrations externes et recevoir des réponses. Par exemple, saisir \"/patient Christelle Durand\" peut retourner les résultats de recherche depuis votre système de santé pour le nom \"Christelle Durand\". Veuillez consulter <a href=\"http://docs.mattermost.com/developer/slash-commands.html\">la documentation des commandes slash</a> pour des instructions plus complètes. Vous pouvez consulter toutes les commandes slash déjà configurées ci-dessous.",
- "user.settings.cmds.add_display_name.placeholder": "Exemple : \"Recherche dossiers médicaux\"",
- "user.settings.cmds.add_new": "Ajouter une nouvelle commande",
- "user.settings.cmds.add_trigger.placeholder": "Déclencheur (par exemple \"hello\"), sans le slash",
- "user.settings.cmds.add_username.placeholder": "Nom d'utilisateur",
- "user.settings.cmds.auto_complete": "Auto-complétion",
- "user.settings.cmds.auto_complete.no": "Non",
- "user.settings.cmds.auto_complete.yes": "Oui",
- "user.settings.cmds.auto_complete_desc": "Description de l'auto-complétion :",
- "user.settings.cmds.auto_complete_desc.placeholder": "Exemple : \"Retourne les résultats de recherche de dossiers médicaux\"",
- "user.settings.cmds.auto_complete_desc_desc": "Description facultative de la commande slash dans la la liste d'auto-complétion.",
- "user.settings.cmds.auto_complete_help": "Afficher cette commande dans la liste d'auto-complétion",
- "user.settings.cmds.auto_complete_hint": "Explication pour l'auto-complétion :",
- "user.settings.cmds.auto_complete_hint.placeholder": "Exemple : [Nom du patient]",
- "user.settings.cmds.auto_complete_hint_desc": "Explication facultative pour la liste d'auto-complétion au sujet des paramètres requis par cette commande slash.",
- "user.settings.cmds.cmd_display_name": "Brève description de la commande slash à afficher dans les listings.",
- "user.settings.cmds.display_name": "Description :",
- "user.settings.cmds.existing": "Commandes existantes",
- "user.settings.cmds.icon_url": "Icône de la réponse :",
- "user.settings.cmds.icon_url_desc": "Choisissez une photo de profil pour les réponses à cette commande slash. Entrez l'URL d'un fichier .png ou .jpg d'au moins 128x128 pixels.",
- "user.settings.cmds.none": "Aucun",
- "user.settings.cmds.regen": "Réinitialiser le jeton",
- "user.settings.cmds.request_type": "Méthode de requête :",
- "user.settings.cmds.request_type_desc": "Le type de méthode de requête HTTP envoyé à cette URL.",
- "user.settings.cmds.request_type_get": "GET",
- "user.settings.cmds.request_type_post": "POST",
- "user.settings.cmds.slashCmd_autocmp": "Autoriser les applications externes à propose l'auto-complétion",
- "user.settings.cmds.token": "Jeton :",
- "user.settings.cmds.trigger": "Mot-clé de déclenchement :",
- "user.settings.cmds.trigger_desc": "Exemples: /patient, /client, /employé Mots réservés : /echo, /join, /logout, /me, /shrug",
- "user.settings.cmds.url": "URL de requête :",
- "user.settings.cmds.url.placeholder": "Doit commencer par http:// ou https://",
- "user.settings.cmds.url_desc": "L'URL de callback qui recevra la requête POST ou GET quand cette commande slash est exécutée.",
- "user.settings.cmds.username": "Utilisateur affiché dans la réponse :",
- "user.settings.cmds.username_desc": "Choisissez un nom d'utilisateur qui sera affiché dans la réponse de la commande slash. Les noms d'utilisateurs peuvent contenir jusqu'à 22 caractères, chiffres, lettres minuscules et symboles \"-\", \"_\" et \".\".",
"user.settings.custom_theme.awayIndicator": "Indicateur \"absent\"",
"user.settings.custom_theme.buttonBg": "Fond de bouton",
"user.settings.custom_theme.buttonColor": "Texte de bouton",
@@ -1277,9 +1263,6 @@
"user.settings.import_theme.importHeader": "Importer un thème Slack",
"user.settings.import_theme.submit": "Envoyer",
"user.settings.import_theme.submitError": "Format invalide, veuillez réessayer de copier-coller.",
- "user.settings.integrations.commands": "Commandes slash",
- "user.settings.integrations.commandsDescription": "Gérez vos commandes slash",
- "user.settings.integrations.title": "Paramètres d'intégration",
"user.settings.languages.change": "Changer la langue de l'interface",
"user.settings.modal.advanced": "Options avancées",
"user.settings.modal.confirmBtns": "Oui, abandonner",
@@ -1288,7 +1271,6 @@
"user.settings.modal.developer": "Développeur",
"user.settings.modal.display": "Affichage",
"user.settings.modal.general": "Général",
- "user.settings.modal.integrations": "Intégrations",
"user.settings.modal.notifications": "Notifications",
"user.settings.modal.security": "Sécurité",
"user.settings.modal.title": "Paramètres du compte",
diff --git a/webapp/i18n/pt.json b/webapp/i18n/pt.json
index bd301cac4..663a02f44 100644
--- a/webapp/i18n/pt.json
+++ b/webapp/i18n/pt.json
@@ -22,6 +22,31 @@
"activity_log_modal.android": "Android",
"activity_log_modal.androidNativeApp": "App Nativo Android",
"activity_log_modal.iphoneNativeApp": "App Nativo para iPhone",
+ "add_command.autocomplete": "Autocompletar",
+ "add_command.autocomplete.help": " Mostrar este comando na lista de preenchimento automático.",
+ "add_command.autocompleteDescription": "Autocompletar Descrição",
+ "add_command.autocompleteDescription.help": "Breve descrição opcional do comando slash para a lista de preenchimento automático.",
+ "add_command.autocompleteDescription.placeholder": "Exemplo: \"Retorna os resultados da pesquisa, prontuário\"",
+ "add_command.autocompleteHint": "Autocompletar Sugestão",
+ "add_command.autocompleteHint.help": "Sugestão opcional na lista autocompletada sobre os parâmetros necessários para o comando.",
+ "add_command.autocompleteHint.placeholder": "Exemplo: [Nome Do Paciente]",
+ "add_command.displayName": "Etiqueta Descritiva",
+ "add_command.iconUrl": "Ícone de Resposta",
+ "add_command.iconUrl.help": "Escolha uma imagem do perfil para substituir as respostas dos posts deste comando slash. Digite a URL de um arquivo .png ou .jpg com pelo menos 128 pixels por 128 pixels.",
+ "add_command.method": "Método de Requisição",
+ "add_command.method.get": "GET",
+ "add_command.method.help": "O tipo de solicitação do comando emitido para a URL requisitada.",
+ "add_command.method.post": "POST",
+ "add_command.trigger": "Comando Palavra Gatilho",
+ "add_command.trigger.help1": "Exemplos: /patient, /client, /employee",
+ "add_command.trigger.help2": "Reserved: /echo, /join, /logout, /me, /shrug",
+ "add_command.trigger.placeholder": "Comando de gatilho ex. \"hello\", não incluí a barra",
+ "add_command.url": "URL de solicitação",
+ "add_command.url.help": "A URL callback para receber o evento HTTP POST ou GET quando o comando slash for executado.",
+ "add_command.url.placeholder": "Deve começar com http:// ou https://",
+ "add_command.username": "Usuário de Resposta",
+ "add_command.username.help": "Escolha um nome de usuário para substituir as respostas deste comando slash. O nome de usuário deve ter até 22 caracteres contendo letras minúsculas, números e os símbolos \"-\", \"_\", e \".\" .",
+ "add_command.username.placeholder": "Usuário",
"add_incoming_webhook.cancel": "Cancelar",
"add_incoming_webhook.channel": "Canal",
"add_incoming_webhook.channelRequired": "Um canal válido é necessário",
@@ -1207,42 +1232,6 @@
"user.settings.advance.sendTitle": "Enviar mensagens Ctrl + Enter",
"user.settings.advance.slashCmd_autocmp": "Ativar aplicação externa para autocompletar comandos slash",
"user.settings.advance.title": "Configurações Avançadas",
- "user.settings.cmds.add": "Adicionar",
- "user.settings.cmds.add_desc": "Criar comandos slash para enviar eventos para integrações externas e receber uma resposta. Por exemplo digitando `/patient Joe Smith` poderia trazer de volta os resultados de pesquisa a partir do seu sistema de gestão de registos internos de saúde para o nome “Joe Smith”. Por favor veja <a href=\"http://docs.mattermost.com/developer/slash-commands.html\">Documentação comandos Slash</a> para detalhes e instruções. Ver todos os comandos slash configurados nesta equipe abaixo.",
- "user.settings.cmds.add_display_name.placeholder": "Exemplo: \"Procurar registros de pacientes\"",
- "user.settings.cmds.add_new": "Adicionar um novo comando",
- "user.settings.cmds.add_trigger.placeholder": "Comando de gatilho ex. \"hello\", não incluí a barra",
- "user.settings.cmds.add_username.placeholder": "Usuário",
- "user.settings.cmds.auto_complete": "Autocompletar: ",
- "user.settings.cmds.auto_complete.no": "não",
- "user.settings.cmds.auto_complete.yes": "sim",
- "user.settings.cmds.auto_complete_desc": "Autocompletar Descrição: ",
- "user.settings.cmds.auto_complete_desc.placeholder": "Exemplo: \"Retorna os resultados da pesquisa, prontuário\"",
- "user.settings.cmds.auto_complete_desc_desc": "Breve descrição opcional do comando slash para a lista de preenchimento automático.",
- "user.settings.cmds.auto_complete_help": " Mostrar este comando na lista de preenchimento automático.",
- "user.settings.cmds.auto_complete_hint": "Autocompletar Sugestão: ",
- "user.settings.cmds.auto_complete_hint.placeholder": "Exemplo: [Nome Do Paciente]",
- "user.settings.cmds.auto_complete_hint_desc": "Sugestão opcional na lista autocompletada sobre os parâmetros necessários para o comando.",
- "user.settings.cmds.cmd_display_name": "Breve descrição do comando slash para mostrar em listas.",
- "user.settings.cmds.display_name": "Etiqueta Descritiva: ",
- "user.settings.cmds.existing": "Comando existente",
- "user.settings.cmds.icon_url": "Ícone de Resposta: ",
- "user.settings.cmds.icon_url_desc": "Escolha uma imagem do perfil para substituir as respostas dos posts deste comando slash. Digite a URL de um arquivo .png ou .jpg com pelo menos 128 pixels por 128 pixels.",
- "user.settings.cmds.none": "Nenhum",
- "user.settings.cmds.regen": "Regen Token",
- "user.settings.cmds.request_type": "Método de Requisição: ",
- "user.settings.cmds.request_type_desc": "O tipo de solicitação do comando emitido para a URL requisitada.",
- "user.settings.cmds.request_type_get": "GET",
- "user.settings.cmds.request_type_post": "POST",
- "user.settings.cmds.slashCmd_autocmp": "Ativar aplicação externa para autocompletar",
- "user.settings.cmds.token": "Token: ",
- "user.settings.cmds.trigger": "Comando Palavra Gatilho: ",
- "user.settings.cmds.trigger_desc": "Exemplos: /patient, /client, /employee Reserved: /echo, /join, /logout, /me, /shrug",
- "user.settings.cmds.url": "URL de solicitação: ",
- "user.settings.cmds.url.placeholder": "Deve começar com http:// ou https://",
- "user.settings.cmds.url_desc": "A URL callback para receber o evento HTTP POST ou GET quando o comando slash for executado.",
- "user.settings.cmds.username": "Usuário de Resposta: ",
- "user.settings.cmds.username_desc": "Escolha um nome de usuário para substituir as respostas deste comando slash. O nome de usuário deve ter até 22 caracteres contendo letras minúsculas, números e os símbolos \"-\", \"_\", e \".\" .",
"user.settings.custom_theme.awayIndicator": "Indicador de Afastamento",
"user.settings.custom_theme.buttonBg": "Fundo Botão",
"user.settings.custom_theme.buttonColor": "Texto do Botão",
@@ -1329,9 +1318,6 @@
"user.settings.import_theme.importHeader": "Importar Tema Slack",
"user.settings.import_theme.submit": "Enviar",
"user.settings.import_theme.submitError": "Formato inválido, por favor tente copiar e colar novamente.",
- "user.settings.integrations.commands": "Comandos Slash",
- "user.settings.integrations.commandsDescription": "Gerenciar seus comandos slash",
- "user.settings.integrations.title": "Configuração de Integração",
"user.settings.languages.change": "Alterar o idioma da interface",
"user.settings.modal.advanced": "Avançado",
"user.settings.modal.confirmBtns": "Sim, Descartar",
@@ -1340,7 +1326,6 @@
"user.settings.modal.developer": "Desenvolvedor",
"user.settings.modal.display": "Exibir",
"user.settings.modal.general": "Geral",
- "user.settings.modal.integrations": "Integrações",
"user.settings.modal.notifications": "Notificações",
"user.settings.modal.security": "Segurança",
"user.settings.modal.title": "Definições de Conta",
diff --git a/webapp/root.jsx b/webapp/root.jsx
index c88b0a7b3..a76f7cf7e 100644
--- a/webapp/root.jsx
+++ b/webapp/root.jsx
@@ -42,6 +42,7 @@ import InstalledIntegrations from 'components/backstage/installed_integrations.j
import AddIntegration from 'components/backstage/add_integration.jsx';
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
import AddOutgoingWebhook from 'components/backstage/add_outgoing_webhook.jsx';
+import AddCommand from 'components/backstage/add_command.jsx';
import ErrorPage from 'components/error_page.jsx';
import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
@@ -285,6 +286,14 @@ function renderRootComponent() {
center: AddOutgoingWebhook
}}
/>
+ <Route
+ path='command'
+ components={{
+ navbar: BackstageNavbar,
+ sidebar: BackstageSidebar,
+ center: AddCommand
+ }}
+ />
</Route>
<Redirect
from='*'
diff --git a/webapp/stores/integration_store.jsx b/webapp/stores/integration_store.jsx
index abd7e3558..d321edec2 100644
--- a/webapp/stores/integration_store.jsx
+++ b/webapp/stores/integration_store.jsx
@@ -20,6 +20,9 @@ class IntegrationStore extends EventEmitter {
this.outgoingWebhooks = [];
this.receivedOutgoingWebhooks = false;
+
+ this.commands = [];
+ this.receivedCommands = false;
}
addChangeListener(callback) {
@@ -61,7 +64,7 @@ class IntegrationStore extends EventEmitter {
}
hasReceivedOutgoingWebhooks() {
- return this.receivedIncomingWebhooks;
+ return this.receivedOutgoingWebhooks;
}
getOutgoingWebhooks() {
@@ -95,6 +98,41 @@ class IntegrationStore extends EventEmitter {
}
}
+ hasReceivedCommands() {
+ return this.receivedCommands;
+ }
+
+ getCommands() {
+ return this.commands;
+ }
+
+ setCommands(commands) {
+ this.commands = commands;
+ this.receivedCommands = true;
+ }
+
+ addCommand(command) {
+ this.commands.push(command);
+ }
+
+ updateCommand(command) {
+ for (let i = 0; i < this.commands.length; i++) {
+ if (this.commands[i].id === command.id) {
+ this.commands[i] = command;
+ break;
+ }
+ }
+ }
+
+ removeCommand(id) {
+ for (let i = 0; i < this.commands.length; i++) {
+ if (this.commands[i].id === id) {
+ this.commands.splice(i, 1);
+ break;
+ }
+ }
+ }
+
handleEventPayload(payload) {
const action = payload.action;
@@ -127,8 +165,27 @@ class IntegrationStore extends EventEmitter {
this.removeOutgoingWebhook(action.id);
this.emitChange();
break;
+ case ActionTypes.RECEIVED_COMMANDS:
+ this.setCommands(action.commands);
+ this.emitChange();
+ break;
+ case ActionTypes.RECEIVED_COMMAND:
+ this.addCommand(action.command);
+ this.emitChange();
+ break;
+ case ActionTypes.UPDATED_COMMAND:
+ this.updateCommand(action.command);
+ this.emitChange();
+ break;
+ case ActionTypes.REMOVED_COMMAND:
+ this.removeCommand(action.id);
+ this.emitChange();
+ break;
}
}
}
-export default new IntegrationStore();
+const instance = new IntegrationStore();
+export default instance;
+window.IntegrationStore = instance;
+
diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx
index db0b2258c..5b0c221ae 100644
--- a/webapp/utils/async_client.jsx
+++ b/webapp/utils/async_client.jsx
@@ -1258,3 +1258,80 @@ export function regenOutgoingHookToken(id) {
}
);
}
+
+export function listTeamCommands() {
+ if (isCallInProgress('listTeamCommands')) {
+ return;
+ }
+
+ callTracker.listTeamCommands = utils.getTimestamp();
+
+ client.listTeamCommands(
+ (data) => {
+ callTracker.listTeamCommands = 0;
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_COMMANDS,
+ commands: data
+ });
+ },
+ (err) => {
+ callTracker.listTeamCommands = 0;
+ dispatchError(err, 'listTeamCommands');
+ }
+ );
+}
+
+export function addCommand(command, success, error) {
+ client.addCommand(
+ command,
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECEIVED_COMMAND,
+ command: data
+ });
+
+ if (success) {
+ success();
+ }
+ },
+ (err) => {
+ if (error) {
+ error(err);
+ } else {
+ dispatchError(err, 'addCommand');
+ }
+ }
+ );
+}
+
+export function deleteCommand(id) {
+ client.deleteCommand(
+ {id},
+ () => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.REMOVED_COMMAND,
+ id
+ });
+ },
+ (err) => {
+ dispatchError(err, 'deleteCommand');
+ }
+ );
+}
+
+export function regenCommandToken(id) {
+ client.regenCommandToken(
+ {id},
+ (data) => {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.UPDATED_COMMAND,
+ command: data
+ });
+ },
+ (err) => {
+ dispatchError(err, 'regenCommandToken');
+ }
+ );
+}
+
diff --git a/webapp/utils/constants.jsx b/webapp/utils/constants.jsx
index d01163b31..ae660a486 100644
--- a/webapp/utils/constants.jsx
+++ b/webapp/utils/constants.jsx
@@ -78,6 +78,10 @@ export default {
RECEIVED_OUTGOING_WEBHOOK: null,
UPDATED_OUTGOING_WEBHOOK: null,
REMOVED_OUTGOING_WEBHOOK: null,
+ RECEIVED_COMMANDS: null,
+ RECEIVED_COMMAND: null,
+ UPDATED_COMMAND: null,
+ REMOVED_COMMAND: null,
RECEIVED_MSG: null,