diff options
author | Brian Olecki <bolecki019@gmail.com> | 2016-11-15 10:43:16 -0500 |
---|---|---|
committer | enahum <nahumhbl@gmail.com> | 2016-11-15 12:43:16 -0300 |
commit | 6297922ab9561dbf774ab5d51619abfc4a411e40 (patch) | |
tree | 84bbced4d8282f5e8af957164d6e92ae50238011 /webapp | |
parent | 1f241af48a9554b6865d1ac79b4261341ab9b3f3 (diff) | |
download | chat-6297922ab9561dbf774ab5d51619abfc4a411e40.tar.gz chat-6297922ab9561dbf774ab5d51619abfc4a411e40.tar.bz2 chat-6297922ab9561dbf774ab5d51619abfc4a411e40.zip |
Add support for editing slash commands (#4335)
Diffstat (limited to 'webapp')
-rw-r--r-- | webapp/client/client.jsx | 12 | ||||
-rw-r--r-- | webapp/components/integrations/components/edit_command.jsx | 731 | ||||
-rw-r--r-- | webapp/components/integrations/components/installed_command.jsx | 9 | ||||
-rw-r--r-- | webapp/i18n/en.json | 7 | ||||
-rw-r--r-- | webapp/routes/route_integrations.jsx | 6 | ||||
-rw-r--r-- | webapp/stores/integration_store.jsx | 9 | ||||
-rw-r--r-- | webapp/tests/client_command.test.jsx | 28 | ||||
-rw-r--r-- | webapp/utils/async_client.jsx | 23 |
8 files changed, 825 insertions, 0 deletions
diff --git a/webapp/client/client.jsx b/webapp/client/client.jsx index 3ce6977f6..90ff75059 100644 --- a/webapp/client/client.jsx +++ b/webapp/client/client.jsx @@ -1457,6 +1457,18 @@ export default class Client { this.track('api', 'api_integrations_created'); } + editCommand(command, success, error) { + request. + post(`${this.getCommandsRoute()}/update`). + set(this.defaultHeaders). + type('application/json'). + accept('application/json'). + send(command). + end(this.handleResponse.bind(this, 'editCommand', success, error)); + + this.track('api', 'api_integrations_created'); + } + deleteCommand(commandId, success, error) { request. post(`${this.getCommandsRoute()}/delete`). diff --git a/webapp/components/integrations/components/edit_command.jsx b/webapp/components/integrations/components/edit_command.jsx new file mode 100644 index 000000000..395c977ca --- /dev/null +++ b/webapp/components/integrations/components/edit_command.jsx @@ -0,0 +1,731 @@ +// 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 IntegrationStore from 'stores/integration_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import {loadTeamCommands} from 'actions/integration_actions.jsx'; +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; +import {FormattedMessage} from 'react-intl'; +import FormError from 'components/form_error.jsx'; +import {browserHistory, Link} from 'react-router/es6'; +import SpinnerButton from 'components/spinner_button.jsx'; +import Constants from 'utils/constants.jsx'; +import ConfirmModal from 'components/confirm_modal.jsx'; + +const REQUEST_POST = 'P'; +const REQUEST_GET = 'G'; + +export default class EditCommand extends React.Component { + static get propTypes() { + return { + team: React.propTypes.object.isRequired, + location: React.PropTypes.object + }; + } + + constructor(props) { + super(props); + + this.handleIntegrationChange = this.handleIntegrationChange.bind(this); + + this.submitCommand = this.submitCommand.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.handleUpdate = this.handleUpdate.bind(this); + this.handleConfirmModal = this.handleConfirmModal.bind(this); + this.confirmModalDismissed = this.confirmModalDismissed.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.originalCommand = null; + this.newCommand = null; + + const teamId = TeamStore.getCurrentId(); + + this.state = { + displayName: '', + description: '', + trigger: '', + url: '', + method: REQUEST_POST, + username: '', + iconUrl: '', + autocomplete: false, + autocompleteHint: '', + autocompleteDescription: '', + saving: false, + serverError: '', + clientError: null, + showConfirmModal: false, + commands: IntegrationStore.getCommands(teamId), + loading: !IntegrationStore.hasReceivedCommands(teamId) + }; + } + + componentDidMount() { + IntegrationStore.addChangeListener(this.handleIntegrationChange); + + if (window.mm_config.EnableCommands === 'true') { + loadTeamCommands(); + } + } + + componentWillUnmount() { + IntegrationStore.removeChangeListener(this.handleIntegrationChange); + } + + handleConfirmModal() { + this.setState({showConfirmModal: true}); + } + + confirmModalDismissed() { + this.setState({showConfirmModal: false}); + } + + submitCommand() { + AsyncClient.editCommand( + this.newCmd, + browserHistory.push('/' + this.props.team.name + '/integrations/commands'), + (err) => { + this.setState({ + saving: false, + serverError: err.message + }); + } + ); + } + + handleUpdate() { + this.setState({ + saving: true, + serverError: '', + clientError: '' + }); + + this.submitCommand(); + } + + handleIntegrationChange() { + const teamId = TeamStore.getCurrentId(); + + this.setState({ + commands: IntegrationStore.getCommands(teamId), + loading: !IntegrationStore.hasReceivedCommands(teamId) + }); + + if (!this.state.loading) { + this.originalCommand = this.state.commands.filter((command) => command.id === this.props.location.query.id)[0]; + + this.setState({ + displayName: this.originalCommand.display_name, + description: this.originalCommand.description, + trigger: this.originalCommand.trigger, + url: this.originalCommand.url, + method: this.originalCommand.method, + username: this.originalCommand.username, + iconUrl: this.originalCommand.icon_url, + autocomplete: this.originalCommand.auto_complete, + autocompleteHint: this.originalCommand.auto_complete_hint, + autocompleteDescription: this.originalCommand.auto_complete_desc + }); + } + } + + handleSubmit(e) { + e.preventDefault(); + + if (this.state.saving) { + return; + } + + this.setState({ + saving: true, + serverError: '', + clientError: '' + }); + + let triggerWord = this.state.trigger.trim().toLowerCase(); + if (triggerWord.indexOf('/') === 0) { + triggerWord = triggerWord.substr(1); + } + + const command = { + display_name: this.state.displayName, + description: this.state.description, + trigger: triggerWord, + url: this.state.url.trim(), + method: this.state.method, + username: this.state.username, + icon_url: this.state.iconUrl, + auto_complete: this.state.autocomplete + }; + + if (this.originalCommand.id) { + command.id = this.originalCommand.id; + } + + 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' + /> + ) + }); + + return; + } + + if (command.trigger.indexOf('/') === 0) { + this.setState({ + saving: false, + clientError: ( + <FormattedMessage + id='add_command.triggerInvalidSlash' + defaultMessage='A trigger word cannot begin with a /' + /> + ) + }); + + return; + } + + if (command.trigger.indexOf(' ') !== -1) { + this.setState({ + saving: false, + clientError: ( + <FormattedMessage + id='add_command.triggerInvalidSpace' + defaultMessage='A trigger word must not contain spaces' + /> + ) + }); + return; + } + + if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH || command.trigger.length > Constants.MAX_TRIGGER_LENGTH) { + this.setState({ + saving: false, + clientError: ( + <FormattedMessage + id='add_command.triggerInvalidLength' + defaultMessage='A trigger word must contain between {min} and {max} characters' + values={{ + min: Constants.MIN_TRIGGER_LENGTH, + max: Constants.MAX_TRIGGER_LENGTH + }} + /> + ) + }); + + return; + } + + if (!command.url) { + this.setState({ + saving: false, + clientError: ( + <FormattedMessage + id='add_command.urlRequired' + defaultMessage='A request URL is required' + /> + ) + }); + + return; + } + + this.newCmd = command; + + if (this.originalCommand.url !== this.newCmd.url || this.originalCommand.trigger !== this.newCmd.trigger || this.originalCommand.method !== this.newCmd.method) { + this.handleConfirmModal(); + this.setState({ + saving: false + }); + } else { + this.submitCommand(); + } + } + + 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() { + const confirmButton = ( + <FormattedMessage + id='update_command.update' + defaultMessage='Update' + /> + ); + + const confirmTitle = ( + <FormattedMessage + id='update_command.confirm' + defaultMessage='Edit Slash Command' + /> + ); + + const confirmMessage = ( + <FormattedMessage + id='update_command.question' + defaultMessage='Your changes may break the existing slash command. Are you sure you would like to update it?' + /> + ); + + let autocompleteFields = null; + if (this.state.autocomplete) { + autocompleteFields = [( + <div + key='autocompleteHint' + className='form-group' + > + <label + className='control-label col-sm-4' + htmlFor='autocompleteHint' + > + <FormattedMessage + id='add_command.autocompleteHint' + defaultMessage='Autocomplete Hint' + /> + </label> + <div className='col-md-5 col-sm-8'> + <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='form__help'> + <FormattedMessage + id='add_command.autocompleteHint.help' + defaultMessage='(Optional) Arguments associated with your slash command, displayed as help in the autocomplete list.' + /> + </div> + </div> + </div> + ), + ( + <div + key='autocompleteDescription' + className='form-group' + > + <label + className='control-label col-sm-4' + htmlFor='autocompleteDescription' + > + <FormattedMessage + id='add_command.autocompleteDescription' + defaultMessage='Autocomplete Description' + /> + </label> + <div className='col-md-5 col-sm-8'> + <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='form__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'> + <BackstageHeader> + <Link to={'/' + this.props.team.name + '/integrations/commands'}> + <FormattedMessage + id='installed_command.header' + defaultMessage='Slash Commands' + /> + </Link> + <FormattedMessage + id='integrations.edit' + defaultMessage='Edit' + /> + </BackstageHeader> + <div className='backstage-form'> + <form + className='form-horizontal' + onSubmit={this.handleSubmit} + > + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='displayName' + > + <FormattedMessage + id='add_command.displayName' + defaultMessage='Display Name' + /> + </label> + <div className='col-md-5 col-sm-8'> + <input + id='displayName' + type='text' + maxLength='64' + className='form-control' + value={this.state.displayName} + onChange={this.updateDisplayName} + /> + <div className='form__help'> + <FormattedMessage + id='add_command.displayName.help' + defaultMessage='Display name for your slash command made of up to 64 characters.' + /> + </div> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='description' + > + <FormattedMessage + id='add_command.description' + defaultMessage='Description' + /> + </label> + <div className='col-md-5 col-sm-8'> + <input + id='description' + type='text' + maxLength='128' + className='form-control' + value={this.state.description} + onChange={this.updateDescription} + /> + <div className='form__help'> + <FormattedMessage + id='add_command.description.help' + defaultMessage='Description for your incoming webhook.' + /> + </div> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='trigger' + > + <FormattedMessage + id='add_command.trigger' + defaultMessage='Command Trigger Word' + /> + </label> + <div className='col-md-5 col-sm-8'> + <input + id='trigger' + type='text' + maxLength={Constants.MAX_TRIGGER_LENGTH} + 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='form__help'> + <FormattedMessage + id='add_command.trigger.help' + defaultMessage='Trigger word must be unique, and cannot begin with a slash or contain any spaces.' + /> + </div> + <div className='form__help'> + <FormattedMessage + id='add_command.trigger.helpExamples' + defaultMessage='Examples: client, employee, patient, weather' + /> + </div> + <div className='form__help'> + <FormattedMessage + id='add_command.trigger.helpReserved' + defaultMessage='Reserved: {link}' + values={{ + link: ( + <a + href='https://docs.mattermost.com/help/messaging/executing-commands.html#built-in-commands' + target='_blank' + rel='noopener noreferrer' + > + <FormattedMessage + id='add_command.trigger.helpReservedLinkText' + defaultMessage='see list of built-in slash commands' + /> + </a> + ) + }} + /> + </div> + </div> + </div> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='url' + > + <FormattedMessage + id='add_command.url' + defaultMessage='Request URL' + /> + </label> + <div className='col-md-5 col-sm-8'> + <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='form__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-4' + htmlFor='method' + > + <FormattedMessage + id='add_command.method' + defaultMessage='Request Method' + /> + </label> + <div className='col-md-5 col-sm-8'> + <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='form__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-label col-sm-4' + htmlFor='username' + > + <FormattedMessage + id='add_command.username' + defaultMessage='Response Username' + /> + </label> + <div className='col-md-5 col-sm-8'> + <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='form__help'> + <FormattedMessage + id='add_command.username.help' + defaultMessage='(Optional) 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> + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='iconUrl' + > + <FormattedMessage + id='add_command.iconUrl' + defaultMessage='Response Icon' + /> + </label> + <div className='col-md-5 col-sm-8'> + <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='form__help'> + <FormattedMessage + id='add_command.iconUrl.help' + defaultMessage='(Optional) 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-4' + htmlFor='autocomplete' + > + <FormattedMessage + id='add_command.autocomplete' + defaultMessage='Autocomplete' + /> + </label> + <div className='col-md-5 col-sm-8 checkbox'> + <input + id='autocomplete' + type='checkbox' + checked={this.state.autocomplete} + onChange={this.updateAutocomplete} + /> + <div className='form__help'> + <FormattedMessage + id='add_command.autocomplete.help' + defaultMessage='(Optional) Show slash command in autocomplete list.' + /> + </div> + </div> + </div> + {autocompleteFields} + <div className='backstage-form__footer'> + <FormError + type='backstage' + errors={[this.state.serverError, this.state.clientError]} + /> + <Link + className='btn btn-sm' + to={'/' + this.props.team.name + '/integrations/commands'} + > + <FormattedMessage + id='add_command.cancel' + defaultMessage='Cancel' + /> + </Link> + <SpinnerButton + className='btn btn-primary' + type='submit' + spinning={this.state.saving} + onClick={this.handleSubmit} + disabled={this.state.loading} + > + <FormattedMessage + id='edit_command.save' + defaultMessage='Update' + /> + </SpinnerButton> + <ConfirmModal + title={confirmTitle} + message={confirmMessage} + confirmButton={confirmButton} + show={this.state.showConfirmModal} + onConfirm={this.handleUpdate} + onCancel={this.confirmModalDismissed} + /> + </div> + </form> + </div> + </div> + ); + } +} diff --git a/webapp/components/integrations/components/installed_command.jsx b/webapp/components/integrations/components/installed_command.jsx index f149a21ac..ecd7d9608 100644 --- a/webapp/components/integrations/components/installed_command.jsx +++ b/webapp/components/integrations/components/installed_command.jsx @@ -130,6 +130,15 @@ export default class InstalledCommand extends React.Component { </a> {' - '} <a + href={'edit?id=' + command.id} + > + <FormattedMessage + id='installed_integrations.edit' + defaultMessage='Edit' + /> + </a> + {' - '} + <a href='#' onClick={this.handleDelete} > diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 532b44c23..f9d91e8e0 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -1157,6 +1157,7 @@ "edit_channel_purpose_modal.save": "Save", "edit_channel_purpose_modal.title1": "Edit Purpose", "edit_channel_purpose_modal.title2": "Edit Purpose for ", + "edit_command.save": "Update", "edit_post.cancel": "Cancel", "edit_post.edit": "Edit {title}", "edit_post.editPost": "Edit the post...", @@ -1362,6 +1363,7 @@ "installed_integrations.content_type": "Content-Type: {contentType}", "installed_integrations.creation": "Created by {creator} on {createAt, date, full}", "installed_integrations.delete": "Delete", + "installed_integrations.edit": "Edit", "installed_integrations.hideSecret": "Hide Secret", "installed_integrations.regenSecret": "Regenerate Secret", "installed_integrations.regenToken": "Regenerate Token", @@ -1396,6 +1398,7 @@ "installed_outgoing_webhooks.search": "Search Outgoing Webhooks", "installed_outgoing_webhooks.unknown_channel": "A Private Webhook", "integrations.add": "Add", + "integrations.edit": "Edit", "integrations.command.description": "Slash commands send events to external integrations", "integrations.command.title": "Slash Command", "integrations.done": "Done", @@ -1788,6 +1791,10 @@ "tutorial_tip.ok": "Okay", "tutorial_tip.out": "Opt out of these tips.", "tutorial_tip.seen": "Seen this before? ", + "update_command.cancel": "Cancel", + "update_command.confirm": "Edit Slash Command", + "update_command.update": "Update", + "update_command.question": "Your changes may break the existing slash command. Are you sure you would like to update it?", "upload_overlay.info": "Drop a file to upload it.", "user.settings.advance.embed_preview": "Show experimental previews of link content, when available", "user.settings.advance.embed_toggle": "Show toggle for all embed previews", diff --git a/webapp/routes/route_integrations.jsx b/webapp/routes/route_integrations.jsx index 0feb13bb7..7a4af7e7a 100644 --- a/webapp/routes/route_integrations.jsx +++ b/webapp/routes/route_integrations.jsx @@ -66,6 +66,12 @@ export default { } }, { + path: 'edit', + getComponents: (location, callback) => { + System.import('components/integrations/components/edit_command.jsx').then(RouteUtils.importComponentSuccess(callback)); + } + }, + { path: 'confirm', getComponents: (location, callback) => { System.import('components/integrations/components/confirm_integration.jsx').then(RouteUtils.importComponentSuccess(callback)); diff --git a/webapp/stores/integration_store.jsx b/webapp/stores/integration_store.jsx index 33680452b..ae818b443 100644 --- a/webapp/stores/integration_store.jsx +++ b/webapp/stores/integration_store.jsx @@ -137,6 +137,15 @@ class IntegrationStore extends EventEmitter { this.setCommands(teamId, commands); } + editCommand(command) { + const teamId = command.team_id; + const commands = this.getCommands(teamId); + + commands.push(command); + + this.setCommands(teamId, commands); + } + updateCommand(command) { const teamId = command.team_id; const commands = this.getCommands(teamId); diff --git a/webapp/tests/client_command.test.jsx b/webapp/tests/client_command.test.jsx index 769fa2fa0..7d39537f8 100644 --- a/webapp/tests/client_command.test.jsx +++ b/webapp/tests/client_command.test.jsx @@ -81,6 +81,34 @@ describe('Client.Commands', function() { }); }); + it('editCommand', function(done) { + TestHelper.initBasic(() => { + TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error + + var cmd = {}; + cmd.url = 'http://www.gonowhere.com'; + cmd.trigger = '/hello'; + cmd.method = 'P'; + cmd.username = ''; + cmd.icon_url = ''; + cmd.auto_complete = false; + cmd.auto_complete_desc = ''; + cmd.auto_complete_hint = ''; + cmd.display_name = 'Unit Test'; + + TestHelper.basicClient().editCommand( + cmd, + function() { + done(new Error('cmds not enabled')); + }, + function(err) { + assert.equal(err.id, 'api.command.disabled.app_error'); + done(); + } + ); + }); + }); + it('deleteCommand', function(done) { TestHelper.initBasic(() => { TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error diff --git a/webapp/utils/async_client.jsx b/webapp/utils/async_client.jsx index efa9eeb2b..fe31d4ef8 100644 --- a/webapp/utils/async_client.jsx +++ b/webapp/utils/async_client.jsx @@ -1348,6 +1348,29 @@ export function addCommand(command, success, error) { ); } +export function editCommand(command, success, error) { + Client.editCommand( + command, + (data) => { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_COMMAND, + command: data + }); + + if (success) { + success(data); + } + }, + (err) => { + if (error) { + error(err); + } else { + dispatchError(err, 'editCommand'); + } + } + ); +} + export function deleteCommand(id) { Client.deleteCommand( id, |