Added basic screen to add incoming webhooks
Этот коммит содержится в:
214
webapp/components/backstage/add_incoming_webhook.jsx
Обычный файл
214
webapp/components/backstage/add_incoming_webhook.jsx
Обычный файл
@@ -0,0 +1,214 @@
|
|||||||
|
// 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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
className='btn btn-sm btn-primary'
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
spinning={this.state.addingUser}
|
spinning={this.state.addingUser}
|
||||||
>
|
>
|
||||||
|
|||||||
50
webapp/components/form_error.jsx
Обычный файл
50
webapp/components/form_error.jsx
Обычный файл
@@ -0,0 +1,50 @@
|
|||||||
|
// 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}) {
|
createJoinDirectChannelButton({user}) {
|
||||||
return (
|
return (
|
||||||
<SpinnerButton
|
<SpinnerButton
|
||||||
|
className='btn btm-sm btn-primary'
|
||||||
spinning={this.state.loadingDMChannel === user.id}
|
spinning={this.state.loadingDMChannel === user.id}
|
||||||
onClick={this.handleShowDirectChannel.bind(this, user)}
|
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() {
|
render() {
|
||||||
if (this.props.spinning) {
|
const {spinning, children, ...props} = this.props; // eslint-disable-line no-use-before-define
|
||||||
|
|
||||||
|
if (spinning) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
className='spinner-button__gif'
|
className='spinner-button__gif'
|
||||||
@@ -38,10 +28,10 @@ export default class SpinnerButton extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={this.handleClick}
|
className='btn btn-primary'
|
||||||
className='btn btn-sm btn-primary'
|
{...props}
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import BackstageNavbar from 'components/backstage/backstage_navbar.jsx';
|
|||||||
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
|
import BackstageSidebar from 'components/backstage/backstage_sidebar.jsx';
|
||||||
import InstalledIntegrations from 'components/backstage/installed_integrations.jsx';
|
import InstalledIntegrations from 'components/backstage/installed_integrations.jsx';
|
||||||
import AddIntegration from 'components/backstage/add_integration.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 SignupTeamComplete from 'components/signup_team_complete/components/signup_team_complete.jsx';
|
||||||
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
|
import WelcomePage from 'components/signup_team_complete/components/team_signup_welcome_page.jsx';
|
||||||
@@ -276,7 +277,7 @@ function renderRootComponent() {
|
|||||||
components={{
|
components={{
|
||||||
navbar: BackstageNavbar,
|
navbar: BackstageNavbar,
|
||||||
sidebar: BackstageSidebar,
|
sidebar: BackstageSidebar,
|
||||||
center: null
|
center: AddIncomingWebhook
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
|
||||||
import Constants from 'utils/constants.jsx';
|
import Constants from 'utils/constants.jsx';
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import * as Utils from 'utils/utils.jsx';
|
|
||||||
|
|
||||||
const ActionTypes = Constants.ActionTypes;
|
const ActionTypes = Constants.ActionTypes;
|
||||||
|
|
||||||
@@ -44,10 +43,14 @@ class IntegrationStore extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIncomingWebhooks(incomingWebhooks) {
|
setIncomingWebhooks(incomingWebhooks) {
|
||||||
this.incomingWebhooks = Utils.freezeArray(incomingWebhooks);
|
this.incomingWebhooks = incomingWebhooks;
|
||||||
this.receivedIncomingWebhooks = true;
|
this.receivedIncomingWebhooks = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addIncomingWebhook(incomingWebhook) {
|
||||||
|
this.incomingWebhooks.push(incomingWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
hasReceivedOutgoingWebhooks() {
|
hasReceivedOutgoingWebhooks() {
|
||||||
return this.receivedIncomingWebhooks;
|
return this.receivedIncomingWebhooks;
|
||||||
}
|
}
|
||||||
@@ -57,10 +60,14 @@ class IntegrationStore extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setOutgoingWebhooks(outgoingWebhooks) {
|
setOutgoingWebhooks(outgoingWebhooks) {
|
||||||
this.outgoingWebhooks = Utils.freezeArray(outgoingWebhooks);
|
this.outgoingWebhooks = outgoingWebhooks;
|
||||||
this.receivedOutgoingWebhooks = true;
|
this.receivedOutgoingWebhooks = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addOutgoingWebhook(outgoingWebhook) {
|
||||||
|
this.outgoingWebhooks.push(outgoingWebhook);
|
||||||
|
}
|
||||||
|
|
||||||
handleEventPayload(payload) {
|
handleEventPayload(payload) {
|
||||||
const action = payload.action;
|
const action = payload.action;
|
||||||
|
|
||||||
@@ -69,10 +76,18 @@ class IntegrationStore extends EventEmitter {
|
|||||||
this.setIncomingWebhooks(action.incomingWebhooks);
|
this.setIncomingWebhooks(action.incomingWebhooks);
|
||||||
this.emitChange();
|
this.emitChange();
|
||||||
break;
|
break;
|
||||||
|
case ActionTypes.RECEIVED_INCOMING_WEBHOOK:
|
||||||
|
this.addIncomingWebhook(action.incomingWebhook);
|
||||||
|
this.emitChange();
|
||||||
|
break;
|
||||||
case ActionTypes.RECEIVED_OUTGOING_WEBHOOKS:
|
case ActionTypes.RECEIVED_OUTGOING_WEBHOOKS:
|
||||||
this.setOutgoingWebhooks(action.outgoingWebhooks);
|
this.setOutgoingWebhooks(action.outgoingWebhooks);
|
||||||
this.emitChange();
|
this.emitChange();
|
||||||
break;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user