Adding edit of incoming webhook (#5272)

Adding edit of outgoing webhook
Fixing spelling of error
Fixing style
Changing from PUT to POST for updates
Fixing test failures due to merge
Этот коммит содержится в:
Poornima
2017-02-27 00:18:20 +05:30
коммит произвёл Joram Wilander
родитель c0bb6f99f8
Коммит 19b753467d
35 изменённых файлов: 1939 добавлений и 763 удалений

Просмотреть файл

@@ -1975,6 +1975,18 @@ export default class Client {
this.trackEvent('api', 'api_integrations_created', {team_id: this.getTeamId()});
}
updateIncomingHook(hook, success, error) {
request.
post(`${this.getHooksRoute()}/incoming/update`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send(hook).
end(this.handleResponse.bind(this, 'updateIncomingHook', success, error));
this.trackEvent('api', 'api_integrations_updated', {team_id: this.getTeamId()});
}
deleteIncomingHook(hookId, success, error) {
request.
post(`${this.getHooksRoute()}/incoming/delete`).
@@ -2008,6 +2020,18 @@ export default class Client {
this.trackEvent('api', 'api_integrations_created', {team_id: this.getTeamId()});
}
updateOutgoingHook(hook, success, error) {
request.
post(`${this.getHooksRoute()}/outgoing/update`).
set(this.defaultHeaders).
type('application/json').
accept('application/json').
send(hook).
end(this.handleResponse.bind(this, 'updateOutgoingHook', success, error));
this.trackEvent('api', 'api_integrations_updated', {team_id: this.getTeamId()});
}
deleteOutgoingHook(hookId, success, error) {
request.
post(`${this.getHooksRoute()}/outgoing/delete`).

Просмотреть файл

@@ -0,0 +1,242 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
import ChannelSelect from 'components/channel_select.jsx';
import {FormattedMessage} from 'react-intl';
import FormError from 'components/form_error.jsx';
import SpinnerButton from 'components/spinner_button.jsx';
import {Link} from 'react-router/es6';
export default class AbstractIncomingWebhook extends React.Component {
static get propTypes() {
return {
team: React.PropTypes.object
};
}
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateDescription = this.updateDescription.bind(this);
this.updateChannelId = this.updateChannelId.bind(this);
this.state = {
displayName: '',
description: '',
channelId: '',
saving: false,
serverError: '',
clientError: null
};
if (typeof this.performAction === 'undefined') {
throw new TypeError('Subclasses must override performAction');
}
if (typeof this.header === 'undefined') {
throw new TypeError('Subclasses must override header');
}
if (typeof this.footer === 'undefined') {
throw new TypeError('Subclasses must override footer');
}
this.performAction = this.performAction.bind(this);
this.header = this.header.bind(this);
this.footer = this.footer.bind(this);
}
handleSubmit(e) {
e.preventDefault();
if (this.state.saving) {
return;
}
this.setState({
saving: true,
serverError: '',
clientError: ''
});
if (!this.state.channelId) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_incoming_webhook.channelRequired'
defaultMessage='A valid channel is required'
/>
)
});
return;
}
const hook = {
channel_id: this.state.channelId,
display_name: this.state.displayName,
description: this.state.description
};
this.performAction(hook);
}
updateDisplayName(e) {
this.setState({
displayName: e.target.value
});
}
updateDescription(e) {
this.setState({
description: e.target.value
});
}
updateChannelId(e) {
this.setState({
channelId: e.target.value
});
}
render() {
var headerToRender = this.header();
var footerToRender = this.footer();
return (
<div className='backstage-content'>
<BackstageHeader>
<Link to={`/${this.props.team.name}/integrations/incoming_webhooks`}>
<FormattedMessage
id='installed_incoming_webhooks.header'
defaultMessage='Incoming Webhooks'
/>
</Link>
<FormattedMessage
id={headerToRender.id}
defaultMessage={headerToRender.defaultMessage}
/>
</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_incoming_webhook.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_incoming_webhook.displayName.help'
defaultMessage='Display name for your incoming webhook 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_incoming_webhook.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_incoming_webhook.description.help'
defaultMessage='Description for your incoming webhook.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='channelId'
>
<FormattedMessage
id='add_incoming_webhook.channel'
defaultMessage='Channel'
/>
</label>
<div className='col-md-5 col-sm-8'>
<ChannelSelect
id='channelId'
value={this.state.channelId}
onChange={this.updateChannelId}
selectOpen={true}
selectPrivate={true}
/>
<div className='form__help'>
<FormattedMessage
id='add_incoming_webhook.channel.help'
defaultMessage='Public channel or private group that receives the webhook payloads. You must belong to the private group when setting up the webhook.'
/>
</div>
</div>
</div>
<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/incoming_webhooks`}
>
<FormattedMessage
id='add_incoming_webhook.cancel'
defaultMessage='Cancel'
/>
</Link>
<SpinnerButton
className='btn btn-primary'
type='submit'
spinning={this.state.saving}
onClick={this.handleSubmit}
>
<FormattedMessage
id={footerToRender.id}
defaultMessage={footerToRender.defaultMessage}
/>
</SpinnerButton>
</div>
</form>
</div>
</div>
);
}
}

Просмотреть файл

@@ -0,0 +1,460 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {localizeMessage} from 'utils/utils.jsx';
import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
import ChannelSelect from 'components/channel_select.jsx';
import {FormattedMessage} from 'react-intl';
import FormError from 'components/form_error.jsx';
import {Link} from 'react-router/es6';
import SpinnerButton from 'components/spinner_button.jsx';
export default class AbstractOutgoingWebhook extends React.Component {
static get propTypes() {
return {
team: React.PropTypes.object
};
}
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateDescription = this.updateDescription.bind(this);
this.updateContentType = this.updateContentType.bind(this);
this.updateChannelId = this.updateChannelId.bind(this);
this.updateTriggerWords = this.updateTriggerWords.bind(this);
this.updateTriggerWhen = this.updateTriggerWhen.bind(this);
this.updateCallbackUrls = this.updateCallbackUrls.bind(this);
this.state = {
displayName: '',
description: '',
contentType: 'application/x-www-form-urlencoded',
channelId: '',
triggerWords: '',
triggerWhen: 0,
callbackUrls: '',
saving: false,
serverError: '',
clientError: null
};
if (typeof this.performAction === 'undefined') {
throw new TypeError('Subclasses must override performAction');
}
if (typeof this.header === 'undefined') {
throw new TypeError('Subclasses must override header');
}
if (typeof this.footer === 'undefined') {
throw new TypeError('Subclasses must override footer');
}
if (typeof this.renderExtra === 'undefined') {
throw new TypeError('Subclasses must override renderExtra');
}
this.performAction = this.performAction.bind(this);
this.header = this.header.bind(this);
this.footer = this.footer.bind(this);
this.renderExtra = this.renderExtra.bind(this);
}
handleSubmit(e) {
e.preventDefault();
if (this.state.saving) {
return;
}
this.setState({
saving: true,
serverError: '',
clientError: ''
});
const triggerWords = [];
if (this.state.triggerWords) {
for (let triggerWord of this.state.triggerWords.split('\n')) {
triggerWord = triggerWord.trim();
if (triggerWord.length > 0) {
triggerWords.push(triggerWord);
}
}
}
if (!this.state.channelId && triggerWords.length === 0) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_outgoing_webhook.triggerWordsOrChannelRequired'
defaultMessage='A valid channel or a list of trigger words is required'
/>
)
});
return;
}
const callbackUrls = [];
for (let callbackUrl of this.state.callbackUrls.split('\n')) {
callbackUrl = callbackUrl.trim();
if (callbackUrl.length > 0) {
callbackUrls.push(callbackUrl);
}
}
if (callbackUrls.length === 0) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_outgoing_webhook.callbackUrlsRequired'
defaultMessage='One or more callback URLs are required'
/>
)
});
return;
}
const hook = {
channel_id: this.state.channelId,
trigger_words: triggerWords,
trigger_when: parseInt(this.state.triggerWhen, 10),
callback_urls: callbackUrls,
display_name: this.state.displayName,
content_type: this.state.contentType,
description: this.state.description
};
this.performAction(hook);
}
updateDisplayName(e) {
this.setState({
displayName: e.target.value
});
}
updateDescription(e) {
this.setState({
description: e.target.value
});
}
updateContentType(e) {
this.setState({
contentType: e.target.value
});
}
updateChannelId(e) {
this.setState({
channelId: e.target.value
});
}
updateTriggerWords(e) {
this.setState({
triggerWords: e.target.value
});
}
updateTriggerWhen(e) {
this.setState({
triggerWhen: e.target.value
});
}
updateCallbackUrls(e) {
this.setState({
callbackUrls: e.target.value
});
}
render() {
const contentTypeOption1 = 'application/x-www-form-urlencoded';
const contentTypeOption2 = 'application/json';
var headerToRender = this.header();
var footerToRender = this.footer();
var renderExtra = this.renderExtra();
return (
<div className='backstage-content'>
<BackstageHeader>
<Link to={`/${this.props.team.name}/integrations/outgoing_webhooks`}>
<FormattedMessage
id='installed_outgoing_webhooks.header'
defaultMessage='Outgoing Webhooks'
/>
</Link>
<FormattedMessage
id={headerToRender.id}
defaultMessage={headerToRender.defaultMessage}
/>
</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_outgoing_webhook.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_outgoing_webhook.displayName.help'
defaultMessage='Display name for your incoming webhook 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_outgoing_webhook.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_outgoing_webhook.description.help'
defaultMessage='Description for your incoming webhook.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='contentType'
>
<FormattedMessage
id='add_outgoing_webhook.content_Type'
defaultMessage='Content Type'
/>
</label>
<div className='col-md-5 col-sm-8'>
<select
className='form-control'
value={this.state.contentType}
onChange={this.updateContentType}
>
<option
value={contentTypeOption1}
>
{contentTypeOption1}
</option>
<option
value={contentTypeOption2}
>
{contentTypeOption2}
</option>
</select>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.contentType.help1'
defaultMessage='Choose the content type by which the response will be sent.'
/>
</div>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.contentType.help2'
defaultMessage='If application/x-www-form-urlencoded is chosen, the server assumes you will be encoding the parameters in a URL format.'
/>
</div>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.contentType.help3'
defaultMessage='If application/json is chosen, the server assumes you will posting JSON data.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='channelId'
>
<FormattedMessage
id='add_outgoing_webhook.channel'
defaultMessage='Channel'
/>
</label>
<div className='col-md-5 col-sm-8'>
<ChannelSelect
id='channelId'
value={this.state.channelId}
onChange={this.updateChannelId}
selectOpen={true}
/>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.channel.help'
defaultMessage='Public channel to receive webhook payloads. Optional if at least one Trigger Word is specified.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='triggerWords'
>
<FormattedMessage
id='add_outgoing_webhook.triggerWords'
defaultMessage='Trigger Words (One Per Line)'
/>
</label>
<div className='col-md-5 col-sm-8'>
<textarea
id='triggerWords'
rows='3'
maxLength='1000'
className='form-control'
value={this.state.triggerWords}
onChange={this.updateTriggerWords}
/>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.triggerWords.help'
defaultMessage='Messages that start with one of the specified words will trigger the outgoing webhook. Optional if Channel is selected.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='triggerWords'
>
<FormattedMessage
id='add_outgoing_webhook.triggerWordsTriggerWhen'
defaultMessage='Trigger When'
/>
</label>
<div className='col-md-5 col-sm-8'>
<select
className='form-control'
value={this.state.triggerWhen}
onChange={this.updateTriggerWhen}
>
<option
value='0'
>
{localizeMessage('add_outgoing_webhook.triggerWordsTriggerWhenFullWord', 'First word matches a trigger word exactly')}
</option>
<option
value='1'
>
{localizeMessage('add_outgoing_webhook.triggerWordsTriggerWhenStartsWith', 'First word starts with a trigger word')}
</option>
</select>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.triggerWordsTriggerWhen.help'
defaultMessage='Choose when to trigger the outgoing webhook; if the first word of a message matches a Trigger Word exactly, or if it starts with a Trigger Word.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='callbackUrls'
>
<FormattedMessage
id='add_outgoing_webhook.callbackUrls'
defaultMessage='Callback URLs (One Per Line)'
/>
</label>
<div className='col-md-5 col-sm-8'>
<textarea
id='callbackUrls'
rows='3'
maxLength='1000'
className='form-control'
value={this.state.callbackUrls}
onChange={this.updateCallbackUrls}
/>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.callbackUrls.help'
defaultMessage='The URL that messages will be sent to.'
/>
</div>
</div>
</div>
<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/outgoing_webhooks`}
>
<FormattedMessage
id='add_outgoing_webhook.cancel'
defaultMessage='Cancel'
/>
</Link>
<SpinnerButton
className='btn btn-primary'
type='submit'
spinning={this.state.saving}
onClick={this.handleSubmit}
>
<FormattedMessage
id={footerToRender.id}
defaultMessage={footerToRender.defaultMessage}
/>
</SpinnerButton>
{renderExtra}
</div>
</form>
</div>
</div>
);
}
}

Просмотреть файл

@@ -1,80 +1,17 @@
// 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/es6';
import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
import ChannelSelect from 'components/channel_select.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';
export default class AddIncomingWebhook extends React.Component {
static get propTypes() {
return {
team: React.PropTypes.object
};
}
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateDescription = this.updateDescription.bind(this);
this.updateChannelId = this.updateChannelId.bind(this);
this.state = {
displayName: '',
description: '',
channelId: '',
saving: false,
serverError: '',
clientError: null
};
}
handleSubmit(e) {
e.preventDefault();
if (this.state.saving) {
return;
}
this.setState({
saving: true,
serverError: '',
clientError: ''
});
if (!this.state.channelId) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_incoming_webhook.channelRequired'
defaultMessage='A valid channel is required'
/>
)
});
return;
}
const hook = {
channel_id: this.state.channelId,
display_name: this.state.displayName,
description: this.state.description
};
import AbstractIncomingWebhook from './abstract_incoming_webhook.jsx';
export default class AddIncomingWebhook extends AbstractIncomingWebhook {
performAction(hook) {
AsyncClient.addIncomingHook(
hook,
(data) => {
browserHistory.push('/' + this.props.team.name + '/integrations/confirm?type=incoming_webhooks&id=' + data.id);
browserHistory.push(`/${this.props.team.name}/integrations/confirm?type=incoming_webhooks&id=${data.id}`);
},
(err) => {
this.setState({
@@ -85,153 +22,11 @@ export default class AddIncomingWebhook extends React.Component {
);
}
updateDisplayName(e) {
this.setState({
displayName: e.target.value
});
header() {
return {id: 'integrations.add', defaultMessage: 'Add'};
}
updateDescription(e) {
this.setState({
description: e.target.value
});
}
updateChannelId(e) {
this.setState({
channelId: e.target.value
});
}
render() {
return (
<div className='backstage-content'>
<BackstageHeader>
<Link to={'/' + this.props.team.name + '/integrations/incoming_webhooks'}>
<FormattedMessage
id='installed_incoming_webhooks.header'
defaultMessage='Incoming Webhooks'
/>
</Link>
<FormattedMessage
id='integrations.add'
defaultMessage='Add'
/>
</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_incoming_webhook.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_incoming_webhook.displayName.help'
defaultMessage='Display name for your incoming webhook 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_incoming_webhook.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_incoming_webhook.description.help'
defaultMessage='Description for your incoming webhook.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='channelId'
>
<FormattedMessage
id='add_incoming_webhook.channel'
defaultMessage='Channel'
/>
</label>
<div className='col-md-5 col-sm-8'>
<ChannelSelect
id='channelId'
value={this.state.channelId}
onChange={this.updateChannelId}
selectOpen={true}
selectPrivate={true}
/>
<div className='form__help'>
<FormattedMessage
id='add_incoming_webhook.channel.help'
defaultMessage='Public channel or private group that receives the webhook payloads. You must belong to the private group when setting up the webhook.'
/>
</div>
</div>
</div>
<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/incoming_webhooks'}
>
<FormattedMessage
id='add_incoming_webhook.cancel'
defaultMessage='Cancel'
/>
</Link>
<SpinnerButton
className='btn btn-primary'
type='submit'
spinning={this.state.saving}
onClick={this.handleSubmit}
>
<FormattedMessage
id='add_incoming_webhook.save'
defaultMessage='Save'
/>
</SpinnerButton>
</div>
</form>
</div>
</div>
);
footer() {
return {id: 'add_incoming_webhook.save', defaultMessage: 'Save'};
}
}

Просмотреть файл

@@ -1,127 +1,17 @@
// 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 {localizeMessage} from 'utils/utils.jsx';
import {browserHistory} from 'react-router/es6';
import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
import ChannelSelect from 'components/channel_select.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';
export default class AddOutgoingWebhook extends React.Component {
static get propTypes() {
return {
team: React.PropTypes.object
};
}
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.updateDisplayName = this.updateDisplayName.bind(this);
this.updateDescription = this.updateDescription.bind(this);
this.updateContentType = this.updateContentType.bind(this);
this.updateChannelId = this.updateChannelId.bind(this);
this.updateTriggerWords = this.updateTriggerWords.bind(this);
this.updateTriggerWhen = this.updateTriggerWhen.bind(this);
this.updateCallbackUrls = this.updateCallbackUrls.bind(this);
this.state = {
displayName: '',
description: '',
contentType: 'application/x-www-form-urlencoded',
channelId: '',
triggerWords: '',
triggerWhen: 0,
callbackUrls: '',
saving: false,
serverError: '',
clientError: null
};
}
handleSubmit(e) {
e.preventDefault();
if (this.state.saving) {
return;
}
this.setState({
saving: true,
serverError: '',
clientError: ''
});
const triggerWords = [];
if (this.state.triggerWords) {
for (let triggerWord of this.state.triggerWords.split('\n')) {
triggerWord = triggerWord.trim();
if (triggerWord.length > 0) {
triggerWords.push(triggerWord);
}
}
}
if (!this.state.channelId && triggerWords.length === 0) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_outgoing_webhook.triggerWordsOrChannelRequired'
defaultMessage='A valid channel or a list of trigger words is required'
/>
)
});
return;
}
const callbackUrls = [];
for (let callbackUrl of this.state.callbackUrls.split('\n')) {
callbackUrl = callbackUrl.trim();
if (callbackUrl.length > 0) {
callbackUrls.push(callbackUrl);
}
}
if (callbackUrls.length === 0) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_outgoing_webhook.callbackUrlsRequired'
defaultMessage='One or more callback URLs are required'
/>
)
});
return;
}
const hook = {
channel_id: this.state.channelId,
trigger_words: triggerWords,
trigger_when: parseInt(this.state.triggerWhen, 10),
callback_urls: callbackUrls,
display_name: this.state.displayName,
content_type: this.state.contentType,
description: this.state.description
};
import AbstractOutgoingWebhook from './abstract_outgoing_webhook.jsx';
export default class AddOutgoingWebhook extends AbstractOutgoingWebhook {
performAction(hook) {
AsyncClient.addOutgoingHook(
hook,
(data) => {
browserHistory.push('/' + this.props.team.name + '/integrations/confirm?type=outgoing_webhooks&id=' + data.id);
browserHistory.push(`/${this.props.team.name}/integrations/confirm?type=outgoing_webhooks&id=${data.id}`);
},
(err) => {
this.setState({
@@ -132,315 +22,15 @@ export default class AddOutgoingWebhook extends React.Component {
);
}
updateDisplayName(e) {
this.setState({
displayName: e.target.value
});
header() {
return {id: 'integrations.add', defaultMessage: 'Add'};
}
updateDescription(e) {
this.setState({
description: e.target.value
});
footer() {
return {id: 'add_outgoing_webhook.save', defaultMessage: 'Save'};
}
updateContentType(e) {
this.setState({
contentType: e.target.value
});
}
updateChannelId(e) {
this.setState({
channelId: e.target.value
});
}
updateTriggerWords(e) {
this.setState({
triggerWords: e.target.value
});
}
updateTriggerWhen(e) {
this.setState({
triggerWhen: e.target.value
});
}
updateCallbackUrls(e) {
this.setState({
callbackUrls: e.target.value
});
}
render() {
const contentTypeOption1 = 'application/x-www-form-urlencoded';
const contentTypeOption2 = 'application/json';
return (
<div className='backstage-content'>
<BackstageHeader>
<Link to={'/' + this.props.team.name + '/integrations/outgoing_webhooks'}>
<FormattedMessage
id='installed_outgoing_webhooks.header'
defaultMessage='Outgoing Webhooks'
/>
</Link>
<FormattedMessage
id='integrations.add'
defaultMessage='Add'
/>
</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_outgoing_webhook.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_outgoing_webhook.displayName.help'
defaultMessage='Display name for your incoming webhook 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_outgoing_webhook.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_outgoing_webhook.description.help'
defaultMessage='Description for your incoming webhook.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='contentType'
>
<FormattedMessage
id='add_outgoing_webhook.content_Type'
defaultMessage='Content Type'
/>
</label>
<div className='col-md-5 col-sm-8'>
<select
className='form-control'
value={this.state.contentType}
onChange={this.updateContentType}
>
<option
value={contentTypeOption1}
>
{contentTypeOption1}
</option>
<option
value={contentTypeOption2}
>
{contentTypeOption2}
</option>
</select>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.contentType.help1'
defaultMessage='Choose the content type by which the response will be sent.'
/>
</div>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.contentType.help2'
defaultMessage='If application/x-www-form-urlencoded is chosen, the server assumes you will be encoding the parameters in a URL format.'
/>
</div>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.contentType.help3'
defaultMessage='If application/json is chosen, the server assumes you will posting JSON data.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='channelId'
>
<FormattedMessage
id='add_outgoing_webhook.channel'
defaultMessage='Channel'
/>
</label>
<div className='col-md-5 col-sm-8'>
<ChannelSelect
id='channelId'
value={this.state.channelId}
onChange={this.updateChannelId}
selectOpen={true}
/>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.channel.help'
defaultMessage='Public channel to receive webhook payloads. Optional if at least one Trigger Word is specified.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='triggerWords'
>
<FormattedMessage
id='add_outgoing_webhook.triggerWords'
defaultMessage='Trigger Words (One Per Line)'
/>
</label>
<div className='col-md-5 col-sm-8'>
<textarea
id='triggerWords'
rows='3'
maxLength='1000'
className='form-control'
value={this.state.triggerWords}
onChange={this.updateTriggerWords}
/>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.triggerWords.help'
defaultMessage='Messages that start with one of the specified words will trigger the outgoing webhook. Optional if Channel is selected.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='triggerWords'
>
<FormattedMessage
id='add_outgoing_webhook.triggerWordsTriggerWhen'
defaultMessage='Trigger When'
/>
</label>
<div className='col-md-5 col-sm-8'>
<select
className='form-control'
value={this.state.triggerWhen}
onChange={this.updateTriggerWhen}
>
<option
value='0'
>
{localizeMessage('add_outgoing_webhook.triggerWordsTriggerWhenFullWord', 'First word matches a trigger word exactly')}
</option>
<option
value='1'
>
{localizeMessage('add_outgoing_webhook.triggerWordsTriggerWhenStartsWith', 'First word starts with a trigger word')}
</option>
</select>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.triggerWordsTriggerWhen.help'
defaultMessage='Choose when to trigger the outgoing webhook; if the first word of a message matches a Trigger Word exactly, or if it starts with a Trigger Word.'
/>
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='callbackUrls'
>
<FormattedMessage
id='add_outgoing_webhook.callbackUrls'
defaultMessage='Callback URLs (One Per Line)'
/>
</label>
<div className='col-md-5 col-sm-8'>
<textarea
id='callbackUrls'
rows='3'
maxLength='1000'
className='form-control'
value={this.state.callbackUrls}
onChange={this.updateCallbackUrls}
/>
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.callbackUrls.help'
defaultMessage='The URL that messages will be sent to.'
/>
</div>
</div>
</div>
<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/outgoing_webhooks'}
>
<FormattedMessage
id='add_outgoing_webhook.cancel'
defaultMessage='Cancel'
/>
</Link>
<SpinnerButton
className='btn btn-primary'
type='submit'
spinning={this.state.saving}
onClick={this.handleSubmit}
>
<FormattedMessage
id='add_outgoing_webhook.save'
defaultMessage='Save'
/>
</SpinnerButton>
</div>
</form>
</div>
</div>
);
renderExtra() {
return '';
}
}

Просмотреть файл

@@ -0,0 +1,78 @@
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router/es6';
import IntegrationStore from 'stores/integration_store.jsx';
import {loadIncomingHooks} from 'actions/integration_actions.jsx';
import AbstractIncomingWebhook from './abstract_incoming_webhook.jsx';
import TeamStore from 'stores/team_store.jsx';
export default class EditIncomingWebhook extends AbstractIncomingWebhook {
constructor(props) {
super(props);
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
this.originalIncomingHook = null;
}
componentDidMount() {
IntegrationStore.addChangeListener(this.handleIntegrationChange);
if (window.mm_config.EnableIncomingWebhooks === 'true') {
loadIncomingHooks();
}
}
componentWillUnmount() {
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
}
handleIntegrationChange() {
const teamId = TeamStore.getCurrentId();
this.setState({
hooks: IntegrationStore.getIncomingWebhooks(teamId),
loading: !IntegrationStore.hasReceivedIncomingWebhooks(teamId)
});
if (!this.state.loading) {
this.originalIncomingHook = this.state.hooks.filter((hook) => hook.id === this.props.location.query.id)[0];
this.setState({
displayName: this.originalIncomingHook.display_name,
description: this.originalIncomingHook.description,
channelId: this.originalIncomingHook.channel_id
});
}
}
performAction(hook) {
if (this.originalIncomingHook.id) {
hook.id = this.originalIncomingHook.id;
}
AsyncClient.updateIncomingHook(
hook,
() => {
browserHistory.push(`/${this.props.team.name}/integrations/incoming_webhooks`);
},
(err) => {
this.setState({
saving: false,
serverError: err.message
});
}
);
}
header() {
return {id: 'integrations.edit', defaultMessage: 'Edit'};
}
footer() {
return {id: 'update_incoming_webhook.update', defaultMessage: 'Update'};
}
}

Просмотреть файл

@@ -0,0 +1,190 @@
// 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/es6';
import IntegrationStore from 'stores/integration_store.jsx';
import {loadOutgoingHooks} from 'actions/integration_actions.jsx';
import AbstractOutgoingWebhook from './abstract_outgoing_webhook.jsx';
import ConfirmModal from 'components/confirm_modal.jsx';
import {FormattedMessage} from 'react-intl';
import TeamStore from 'stores/team_store.jsx';
export default class EditOutgoingWebhook extends AbstractOutgoingWebhook {
constructor(props) {
super(props);
this.handleIntegrationChange = this.handleIntegrationChange.bind(this);
this.handleConfirmModal = this.handleConfirmModal.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.submitCommand = this.submitCommand.bind(this);
this.confirmModalDismissed = this.confirmModalDismissed.bind(this);
this.originalOutgoingHook = null;
this.state = {
showConfirmModal: false
};
}
componentDidMount() {
IntegrationStore.addChangeListener(this.handleIntegrationChange);
if (window.mm_config.EnableOutgoingWebhooks === 'true') {
loadOutgoingHooks();
}
}
componentWillUnmount() {
IntegrationStore.removeChangeListener(this.handleIntegrationChange);
}
handleIntegrationChange() {
const teamId = TeamStore.getCurrentId();
this.setState({
hooks: IntegrationStore.getOutgoingWebhooks(teamId),
loading: !IntegrationStore.hasReceivedOutgoingWebhooks(teamId)
});
if (!this.state.loading) {
this.originalOutgoingHook = this.state.hooks.filter((hook) => hook.id === this.props.location.query.id)[0];
this.setState({
displayName: this.originalOutgoingHook.display_name,
description: this.originalOutgoingHook.description,
channelId: this.originalOutgoingHook.channel_id,
contentType: this.originalOutgoingHook.content_type,
triggerWhen: this.originalOutgoingHook.trigger_when
});
var triggerWords = '';
if (this.originalOutgoingHook.trigger_words) {
let i = 0;
for (i = 0; i < this.originalOutgoingHook.trigger_words.length; i++) {
triggerWords += this.originalOutgoingHook.trigger_words[i] + '\n';
}
}
var callbackUrls = '';
if (this.originalOutgoingHook.callback_urls) {
let i = 0;
for (i = 0; i < this.originalOutgoingHook.callback_urls.length; i++) {
callbackUrls += this.originalOutgoingHook.callback_urls[i] + '\n';
}
}
this.setState({
triggerWords,
callbackUrls
});
}
}
performAction(hook) {
this.newHook = hook;
if (this.originalOutgoingHook.id) {
hook.id = this.originalOutgoingHook.id;
}
if (this.originalOutgoingHook.token) {
hook.token = this.originalOutgoingHook.token;
}
var triggerWordsSame = (this.originalOutgoingHook.trigger_words.length === hook.trigger_words.length) &&
this.originalOutgoingHook.trigger_words.every((v, i) => v === hook.trigger_words[i]);
var callbackUrlsSame = (this.originalOutgoingHook.callback_urls.length === hook.callback_urls.length) &&
this.originalOutgoingHook.callback_urls.every((v, i) => v === hook.callback_urls[i]);
if (this.originalOutgoingHook.content_type !== hook.content_type ||
!triggerWordsSame || !callbackUrlsSame) {
this.handleConfirmModal();
this.setState({
saving: false
});
} else {
this.submitCommand();
}
}
handleUpdate() {
this.setState({
saving: true,
serverError: '',
clientError: ''
});
this.submitCommand();
}
handleConfirmModal() {
this.setState({showConfirmModal: true});
}
confirmModalDismissed() {
this.setState({showConfirmModal: false});
}
submitCommand() {
AsyncClient.updateOutgoingHook(
this.newHook,
() => {
browserHistory.push(`/${this.props.team.name}/integrations/outgoing_webhooks`);
},
(err) => {
this.setState({
saving: false,
showConfirmModal: false,
serverError: err.message
});
}
);
}
header() {
return {id: 'integrations.edit', defaultMessage: 'Edit'};
}
footer() {
return {id: 'update_outgoing_webhook.update', defaultMessage: 'Update'};
}
renderExtra() {
const confirmButton = (
<FormattedMessage
id='update_outgoing_webhook.update'
defaultMessage='Update'
/>
);
const confirmTitle = (
<FormattedMessage
id='update_outgoing_webhook.confirm'
defaultMessage='Edit Outgoing Webhook'
/>
);
const confirmMessage = (
<FormattedMessage
id='update_outgoing_webhook.question'
defaultMessage='Your changes may break the existing outgoing webhook. Are you sure you would like to update it?'
/>
);
return (
<ConfirmModal
title={confirmTitle}
message={confirmMessage}
confirmButton={confirmButton}
show={this.state.showConfirmModal}
onConfirm={this.handleUpdate}
onCancel={this.confirmModalDismissed}
/>
);
}
}

Просмотреть файл

@@ -7,6 +7,7 @@ import ChannelStore from 'stores/channel_store.jsx';
import {getSiteURL} from 'utils/url.jsx';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router';
export default class InstalledIncomingWebhook extends React.Component {
static get propTypes() {
@@ -15,7 +16,8 @@ export default class InstalledIncomingWebhook extends React.Component {
onDelete: React.PropTypes.func.isRequired,
filter: React.PropTypes.string,
creator: React.PropTypes.object.isRequired,
canChange: React.PropTypes.bool.isRequired
canChange: React.PropTypes.bool.isRequired,
team: React.PropTypes.object.isRequired
};
}
@@ -88,6 +90,13 @@ export default class InstalledIncomingWebhook extends React.Component {
if (this.props.canChange) {
actions = (
<div className='item-actions'>
<Link to={`/${this.props.team.name}/integrations/incoming_webhooks/edit?id=${incomingWebhook.id}`}>
<FormattedMessage
id='installed_integrations.edit'
defaultMessage='Edit'
/>
</Link>
{' - '}
<a
href='#'
onClick={this.handleDelete}

Просмотреть файл

@@ -110,6 +110,7 @@ export default class InstalledIncomingWebhooks extends React.Component {
onDelete={this.deleteIncomingWebhook}
creator={this.state.users[incomingWebhook.user_id] || {}}
canChange={canChange}
team={this.props.team}
/>
);
});

Просмотреть файл

@@ -6,6 +6,7 @@ import React from 'react';
import ChannelStore from 'stores/channel_store.jsx';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router';
export default class InstalledOutgoingWebhook extends React.Component {
static get propTypes() {
@@ -15,7 +16,8 @@ export default class InstalledOutgoingWebhook extends React.Component {
onDelete: React.PropTypes.func.isRequired,
filter: React.PropTypes.string,
creator: React.PropTypes.object.isRequired,
canChange: React.PropTypes.bool.isRequired
canChange: React.PropTypes.bool.isRequired,
team: React.PropTypes.object.isRequired
};
}
@@ -161,6 +163,13 @@ export default class InstalledOutgoingWebhook extends React.Component {
/>
</a>
{' - '}
<Link to={`/${this.props.team.name}/integrations/outgoing_webhooks/edit?id=${outgoingWebhook.id}`}>
<FormattedMessage
id='installed_integrations.edit'
defaultMessage='Edit'
/>
</Link>
{' - '}
<a
href='#'
onClick={this.handleDelete}

Просмотреть файл

@@ -114,6 +114,7 @@ export default class InstalledOutgoingWebhooks extends React.Component {
onDelete={this.deleteOutgoingWebhook}
creator={this.state.users[outgoingWebhook.creator_id] || {}}
canChange={canChange}
team={this.props.team}
/>
);
});

Просмотреть файл

@@ -96,6 +96,7 @@
"add_incoming_webhook.name": "Name",
"add_incoming_webhook.save": "Save",
"add_incoming_webhook.url": "<b>URL</b>: {url}",
"update_incoming_webhook.update": "Update",
"add_oauth_app.callbackUrls.help": "The redirect URIs to which the service will redirect users after accepting or denying authorization of your application, and which will handle authorization codes or access tokens. Must be a valid URL and start with http:// or https://.",
"add_oauth_app.callbackUrlsRequired": "One or more callback URLs are required",
"add_oauth_app.clientId": "<b>Client ID</b>: {id}",
@@ -137,6 +138,9 @@
"add_outgoing_webhook.triggerWordsTriggerWhen.help": "Choose when to trigger the outgoing webhook; if the first word of a message matches a Trigger Word exactly, or if it starts with a Trigger Word.",
"add_outgoing_webhook.triggerWordsTriggerWhenFullWord": "First word matches a trigger word exactly",
"add_outgoing_webhook.triggerWordsTriggerWhenStartsWith": "First word starts with a trigger word",
"update_outgoing_webhook.update": "Update",
"update_outgoing_webhook.confirm": "Edit Outgoing Webhook",
"update_outgoing_webhook.question": "Your changes may break the existing outgoing webhook. Are you sure you would like to update it?",
"admin.advance.cluster": "High Availability (Beta)",
"admin.advance.metrics": "Performance Monitoring (Beta)",
"admin.audits.reload": "Reload User Activity Logs",

Просмотреть файл

@@ -27,6 +27,12 @@ export default {
getComponents: (location, callback) => {
System.import('components/integrations/components/add_incoming_webhook.jsx').then(RouteUtils.importComponentSuccess(callback));
}
},
{
path: 'edit',
getComponents: (location, callback) => {
System.import('components/integrations/components/edit_incoming_webhook.jsx').then(RouteUtils.importComponentSuccess(callback));
}
}
]
},
@@ -43,6 +49,12 @@ export default {
getComponents: (location, callback) => {
System.import('components/integrations/components/add_outgoing_webhook.jsx').then(RouteUtils.importComponentSuccess(callback));
}
},
{
path: 'edit',
getComponents: (location, callback) => {
System.import('components/integrations/components/edit_outgoing_webhook.jsx').then(RouteUtils.importComponentSuccess(callback));
}
}
]
},

Просмотреть файл

@@ -57,6 +57,20 @@ class IntegrationStore extends EventEmitter {
this.setIncomingWebhooks(teamId, incomingWebhooks);
}
updateIncomingWebhook(incomingWebhook) {
const teamId = incomingWebhook.team_id;
const incomingWebhooks = this.getIncomingWebhooks(teamId);
for (let i = 0; i < incomingWebhooks.length; i++) {
if (incomingWebhooks[i].id === incomingWebhook.id) {
incomingWebhooks[i] = incomingWebhook;
break;
}
}
this.setIncomingWebhooks(teamId, incomingWebhooks);
}
removeIncomingWebhook(teamId, id) {
let incomingWebhooks = this.getIncomingWebhooks(teamId);
@@ -200,6 +214,10 @@ class IntegrationStore extends EventEmitter {
this.addIncomingWebhook(action.incomingWebhook);
this.emitChange();
break;
case ActionTypes.UPDATED_INCOMING_WEBHOOK:
this.updateIncomingWebhook(action.incomingWebhook);
this.emitChange();
break;
case ActionTypes.REMOVED_INCOMING_WEBHOOK:
this.removeIncomingWebhook(action.teamId, action.id);
this.emitChange();

Просмотреть файл

@@ -22,7 +22,29 @@ describe('Client.Hooks', function() {
done(new Error('hooks not enabled'));
},
function(err) {
assert.equal(err.id, 'api.webhook.create_incoming.disabled.app_errror');
assert.equal(err.id, 'api.webhook.create_incoming.disabled.app_error');
done();
}
);
});
});
it('updateIncomingHook', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
var hook = {};
hook.channel_id = TestHelper.basicChannel().id;
hook.description = 'desc';
hook.display_name = 'Unit Test';
TestHelper.basicClient().updateIncomingHook(
hook,
function() {
done(new Error('hooks not enabled'));
},
function(err) {
assert.equal(err.id, 'api.webhook.update_incoming.disabled.app_error');
done();
}
);
@@ -38,7 +60,7 @@ describe('Client.Hooks', function() {
done(new Error('hooks not enabled'));
},
function(err) {
assert.equal(err.id, 'api.webhook.delete_incoming.disabled.app_errror');
assert.equal(err.id, 'api.webhook.delete_incoming.disabled.app_error');
done();
}
);
@@ -128,5 +150,27 @@ describe('Client.Hooks', function() {
);
});
});
it('updateOutgoingHook', function(done) {
TestHelper.initBasic(() => {
TestHelper.basicClient().enableLogErrorsToConsole(false); // Disabling since this unit test causes an error
var hook = {};
hook.channel_id = TestHelper.basicChannel().id;
hook.description = 'desc';
hook.display_name = 'Unit Test';
TestHelper.basicClient().updateOutgoingHook(
hook,
function() {
done(new Error('hooks not enabled'));
},
function(err) {
assert.equal(err.id, 'api.webhook.update_outgoing.disabled.app_error');
done();
}
);
});
});
});

Просмотреть файл

@@ -1324,6 +1324,29 @@ export function addIncomingHook(hook, success, error) {
);
}
export function updateIncomingHook(hook, success, error) {
Client.updateIncomingHook(
hook,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.UPDATED_INCOMING_WEBHOOK,
incomingWebhook: data
});
if (success) {
success(data);
}
},
(err) => {
if (error) {
error(err);
} else {
dispatchError(err, 'updateIncomingHook');
}
}
);
}
export function addOutgoingHook(hook, success, error) {
Client.addOutgoingHook(
hook,
@@ -1347,6 +1370,29 @@ export function addOutgoingHook(hook, success, error) {
);
}
export function updateOutgoingHook(hook, success, error) {
Client.updateOutgoingHook(
hook,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.UPDATED_OUTGOING_WEBHOOK,
outgoingWebhook: data
});
if (success) {
success(data);
}
},
(err) => {
if (error) {
error(err);
} else {
dispatchError(err, 'updateOutgoingHook');
}
}
);
}
export function deleteIncomingHook(id) {
Client.deleteIncomingHook(
id,

Просмотреть файл

@@ -105,6 +105,7 @@ export const ActionTypes = keyMirror({
RECEIVED_INCOMING_WEBHOOKS: null,
RECEIVED_INCOMING_WEBHOOK: null,
UPDATED_INCOMING_WEBHOOK: null,
REMOVED_INCOMING_WEBHOOK: null,
RECEIVED_OUTGOING_WEBHOOKS: null,
RECEIVED_OUTGOING_WEBHOOK: null,