Commit 12cc60ab authored by Harrison Healey's avatar Harrison Healey

Added basic screen to add incoming webhooks

parent a8d58447
// 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 ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.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';
export default class AddIncomingWebhook extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.updateName = this.updateName.bind(this);
this.updateDescription = this.updateDescription.bind(this);
this.updateChannelName = this.updateChannelName.bind(this);
this.state = {
team: TeamStore.getCurrent(),
name: '',
description: '',
channelName: '',
saving: false,
serverError: '',
clientError: null
};
}
componentDidMount() {
TeamStore.addChangeListener(this.handleChange);
}
componentWillUnmount() {
TeamStore.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
team: TeamStore.getCurrent()
});
}
handleSubmit(e) {
e.preventDefault();
if (this.state.saving) {
return;
}
this.setState({
saving: true,
serverError: '',
clientError: ''
});
const channel = ChannelStore.getByName(this.state.channelName);
if (!channel) {
this.setState({
saving: false,
clientError: (
<FormattedMessage
id='add_incoming_webhook.channel_name_required'
defaultMessage='A valid channel name (eg. town-square) is required'
/>
)
});
return;
}
const hook = {
channel_id: channel.id
};
AsyncClient.addIncomingHook(
hook,
() => {
browserHistory.push(`/${this.state.team.name}/integrations/installed`);
},
(err) => {
this.setState({
serverError: err.message
});
}
);
}
updateName(e) {
this.setState({
name: e.target.value
});
}
updateDescription(e) {
this.setState({
description: e.target.value
});
}
updateChannelName(e) {
this.setState({
channelName: e.target.value
});
}
render() {
const team = TeamStore.getCurrent();
if (!team) {
return null;
}
return (
<div className='backstage row'>
<div className='add-incoming-webhook'>
<div className='backstage__header'>
<h1 className='text'>
<FormattedMessage
id='add-incoming-webhook.header'
defaultMessage='Add Incoming Webhook'
/>
</h1>
</div>
</div>
<form className='add-incoming-webhook__body'>
<div className='add-integration__row'>
<label
className='add-integration__label'
htmlFor='name'
>
<FormattedMessage
id='add-incoming-webhook.name'
defaultMessage='Name'
/>
</label>
<input
id='name'
type='text'
value={this.state.name}
onChange={this.updateName}
/>
</div>
<div className='add-integration__row'>
<label
className='add-integration__label'
htmlFor='description'
>
<FormattedMessage
id='add-incoming-webhook.description'
defaultMessage='Description'
/>
</label>
<input
id='description'
type='text'
value={this.state.description}
onChange={this.updateDescription}
/>
</div>
<div className='add-integration__row'>
<label
className='add-integration__label'
htmlFor='channelName'
>
<FormattedMessage
id='add-incoming-webhook.channelName'
defaultMessage='Channel Name'
/>
</label>
<input
id='channelName'
type='text'
value={this.state.channelName}
onChange={this.updateChannelName}
/>
</div>
<div className='add-integration__submit-row'>
<Link
className='btn btn-sm'
to={`/${team.name}/integrations/add`}
>
<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>
<FormError errors={[this.state.serverError, this.state.clientError]}/>
</form>
</div>
);
}
}
......@@ -65,6 +65,7 @@ export default class ChannelInviteButton extends React.Component {
render() {
return (
<SpinnerButton
className='btn btn-sm btn-primary'
onClick={this.handleClick}
spinning={this.state.addingUser}
>
......
// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
export default class FormError extends React.Component {
static get propTypes() {
// accepts either a single error or an array of errors
return {
error: React.PropTypes.node,
errors: React.PropTypes.arrayOf(React.PropTypes.node)
};
}
static get defaultProps() {
return {
error: null,
errors: []
};
}
render() {
if (!this.props.error && this.props.errors.length === 0) {
return null;
}
// look for the first truthy error to display
let message = this.props.error;
if (!message) {
for (const error of this.props.errors) {
if (error) {
message = error;
}
}
}
if (!message) {
return null;
}
return (
<div className='form-group has-error'>
<label className='control-label'>
{message}
</label>
</div>
);
}
}
......@@ -86,6 +86,7 @@ export default class MoreDirectChannels extends React.Component {
createJoinDirectChannelButton({user}) {
return (
<SpinnerButton
className='btn btm-sm btn-primary'
spinning={this.state.loadingDMChannel === user.id}
onClick={this.handleShowDirectChannel.bind(this, user)}
>
......
......@@ -14,20 +14,10 @@ export default class SpinnerButton extends React.Component {
};
}
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
if (this.props.onClick) {
this.props.onClick(e);
}
}
render() {
if (this.props.spinning) {
const {spinning, children, ...props} = this.props; // eslint-disable-line no-use-before-define
if (spinning) {
return (
<img
className='spinner-button__gif'
......@@ -38,10 +28,10 @@ export default class SpinnerButton extends React.Component {
return (
<button
onClick={this.handleClick}
className='btn btn-sm btn-primary'
className='btn btn-primary'
{...props}
>
{this.props.children}
{children}
</button>
);
}
......
......@@ -40,6 +40,7 @@ import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
import InstalledIntegrations from 'components/backstage/installed_integrations.jsx';
import AddIntegration from 'components/backstage/add_integration.jsx';
import AddIncomingWebhook from 'components/backstage/add_incoming_webhook.jsx';
import SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
......@@ -276,7 +277,7 @@ function renderRootComponent() {
components={{
navbar: BackstageNavbar,
sidebar: BackstageSidebar,
center: null
center: AddIncomingWebhook
}}
/>
<Route
......
......@@ -4,7 +4,6 @@
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import Constants from 'utils/constants.jsx';
import EventEmitter from 'events';
import * as Utils from 'utils/utils.jsx';
const ActionTypes = Constants.ActionTypes;
......@@ -44,10 +43,14 @@ class IntegrationStore extends EventEmitter {
}
setIncomingWebhooks(incomingWebhooks) {
this.incomingWebhooks = Utils.freezeArray(incomingWebhooks);
this.incomingWebhooks = incomingWebhooks;
this.receivedIncomingWebhooks = true;
}
addIncomingWebhook(incomingWebhook) {
this.incomingWebhooks.push(incomingWebhook);
}
hasReceivedOutgoingWebhooks() {
return this.receivedIncomingWebhooks;
}
......@@ -57,10 +60,14 @@ class IntegrationStore extends EventEmitter {
}
setOutgoingWebhooks(outgoingWebhooks) {
this.outgoingWebhooks = Utils.freezeArray(outgoingWebhooks);
this.outgoingWebhooks = outgoingWebhooks;
this.receivedOutgoingWebhooks = true;
}
addOutgoingWebhook(outgoingWebhook) {
this.outgoingWebhooks.push(outgoingWebhook);
}
handleEventPayload(payload) {
const action = payload.action;
......@@ -69,10 +76,18 @@ class IntegrationStore extends EventEmitter {
this.setIncomingWebhooks(action.incomingWebhooks);
this.emitChange();
break;
case ActionTypes.RECEIVED_INCOMING_WEBHOOK:
this.addIncomingWebhook(action.incomingWebhook);
this.emitChange();
break;
case ActionTypes.RECEIVED_OUTGOING_WEBHOOKS:
this.setOutgoingWebhooks(action.outgoingWebhooks);
this.emitChange();
break;
case ActionTypes.RECEIVED_OUTGOING_WEBHOOK:
this.addOutgoingWebhook(action.outgoingWebhook);
this.emitChange();
break;
}
}
}
......
......@@ -1167,3 +1167,49 @@ export function listOutgoingHooks() {
}
);
}
export function addIncomingHook(hook, success, error) {
client.addIncomingHook(
hook,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_INCOMING_WEBHOOK,
incomingWebhook: data
});
if (success) {
success();
}
},
(err) => {
dispatchError(err, 'addIncomingHook');
if (error) {
error(err);
}
}
);
}
export function addOutgoingHook(hook, success, error) {
client.addOutgoingHook(
hook,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_OUTGOING_WEBHOOK,
outgoingWebhook: data
});
if (success) {
success();
}
},
(err) => {
dispatchError(err, 'addOutgoingHook');
if (error) {
error(err);
}
}
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment