diff options
13 files changed, 480 insertions, 297 deletions
diff --git a/model/outgoing_webhook.go b/model/outgoing_webhook.go index 3cfed9e74..70c65bec7 100644 --- a/model/outgoing_webhook.go +++ b/model/outgoing_webhook.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "io" + "net/http" "net/url" "strconv" "strings" @@ -112,69 +113,69 @@ func OutgoingWebhookListFromJson(data io.Reader) []*OutgoingWebhook { func (o *OutgoingWebhook) IsValid() *AppError { if len(o.Id) != 26 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.id.app_error", nil, "", http.StatusBadRequest) } if len(o.Token) != 26 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.token.app_error", nil, "", http.StatusBadRequest) } if o.CreateAt == 0 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id) + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) } if o.UpdateAt == 0 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id) + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) } if len(o.CreatorId) != 26 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.user_id.app_error", nil, "", http.StatusBadRequest) } if len(o.ChannelId) != 0 && len(o.ChannelId) != 26 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.channel_id.app_error", nil, "", http.StatusBadRequest) } if len(o.TeamId) != 26 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.team_id.app_error", nil, "", http.StatusBadRequest) } if len(fmt.Sprintf("%s", o.TriggerWords)) > 1024 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.words.app_error", nil, "", http.StatusBadRequest) } if len(o.TriggerWords) != 0 { for _, triggerWord := range o.TriggerWords { if len(triggerWord) == 0 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.trigger_words.app_error", nil, "", http.StatusBadRequest) } } } if len(o.CallbackURLs) == 0 || len(fmt.Sprintf("%s", o.CallbackURLs)) > 1024 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.callback.app_error", nil, "", http.StatusBadRequest) } for _, callback := range o.CallbackURLs { if !IsValidHttpUrl(callback) { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.url.app_error", nil, "", http.StatusBadRequest) } } if len(o.DisplayName) > 64 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.display_name.app_error", nil, "", http.StatusBadRequest) } if len(o.Description) > 128 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.description.app_error", nil, "", http.StatusBadRequest) } if len(o.ContentType) > 128 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest) } if o.TriggerWhen > 1 { - return NewLocAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "") + return NewAppError("OutgoingWebhook.IsValid", "model.outgoing_hook.is_valid.content_type.app_error", nil, "", http.StatusBadRequest) } return nil diff --git a/webapp/components/integrations/components/abstract_outgoing_webhook.jsx b/webapp/components/integrations/components/abstract_outgoing_webhook.jsx index 912ad3bdf..397423395 100644 --- a/webapp/components/integrations/components/abstract_outgoing_webhook.jsx +++ b/webapp/components/integrations/components/abstract_outgoing_webhook.jsx @@ -16,61 +16,81 @@ 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: PropTypes.object - }; + static propTypes = { + + /** + * The current team + */ + team: PropTypes.object.isRequired, + + /** + * The header text to render, has id and defaultMessage + */ + header: PropTypes.object.isRequired, + + /** + * The footer text to render, has id and defaultMessage + */ + footer: PropTypes.object.isRequired, + + /** + * Any extra component/node to render + */ + renderExtra: PropTypes.node.isRequired, + + /** + * The server error text after a failed action + */ + serverError: PropTypes.string.isRequired, + + /** + * The hook used to set the initial state + */ + initialHook: PropTypes.object, + + /** + * The async function to run when the action button is pressed + */ + action: PropTypes.func.isRequired } 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'); - } + this.state = this.getStateFromHook(this.props.initialHook || {}); + } - if (typeof this.footer === 'undefined') { - throw new TypeError('Subclasses must override footer'); + getStateFromHook = (hook) => { + let triggerWords = ''; + if (hook.trigger_words) { + let i = 0; + for (i = 0; i < hook.trigger_words.length; i++) { + triggerWords += hook.trigger_words[i] + '\n'; + } } - if (typeof this.renderExtra === 'undefined') { - throw new TypeError('Subclasses must override renderExtra'); + let callbackUrls = ''; + if (hook.callback_urls) { + let i = 0; + for (i = 0; i < hook.callback_urls.length; i++) { + callbackUrls += hook.callback_urls[i] + '\n'; + } } - this.performAction = this.performAction.bind(this); - this.header = this.header.bind(this); - this.footer = this.footer.bind(this); - this.renderExtra = this.renderExtra.bind(this); + return { + displayName: hook.display_name || '', + description: hook.description || '', + contentType: hook.content_type || 'application/x-www-form-urlencoded', + channelId: hook.channel_id || '', + triggerWords, + triggerWhen: hook.trigger_when || 0, + callbackUrls, + saving: false, + clientError: null + }; } - handleSubmit(e) { + handleSubmit = (e) => { e.preventDefault(); if (this.state.saving) { @@ -79,7 +99,6 @@ export default class AbstractOutgoingWebhook extends React.Component { this.setState({ saving: true, - serverError: '', clientError: '' }); @@ -142,46 +161,46 @@ export default class AbstractOutgoingWebhook extends React.Component { description: this.state.description }; - this.performAction(hook); + this.props.action(hook).then(() => this.setState({saving: false})); } - updateDisplayName(e) { + updateDisplayName = (e) => { this.setState({ displayName: e.target.value }); } - updateDescription(e) { + updateDescription = (e) => { this.setState({ description: e.target.value }); } - updateContentType(e) { + updateContentType = (e) => { this.setState({ contentType: e.target.value }); } - updateChannelId(e) { + updateChannelId = (e) => { this.setState({ channelId: e.target.value }); } - updateTriggerWords(e) { + updateTriggerWords = (e) => { this.setState({ triggerWords: e.target.value }); } - updateTriggerWhen(e) { + updateTriggerWhen = (e) => { this.setState({ triggerWhen: e.target.value }); } - updateCallbackUrls(e) { + updateCallbackUrls = (e) => { this.setState({ callbackUrls: e.target.value }); @@ -191,9 +210,9 @@ export default class AbstractOutgoingWebhook extends React.Component { const contentTypeOption1 = 'application/x-www-form-urlencoded'; const contentTypeOption2 = 'application/json'; - var headerToRender = this.header(); - var footerToRender = this.footer(); - var renderExtra = this.renderExtra(); + var headerToRender = this.props.header; + var footerToRender = this.props.footer; + var renderExtra = this.props.renderExtra; return ( <div className='backstage-content'> @@ -432,7 +451,7 @@ export default class AbstractOutgoingWebhook extends React.Component { <div className='backstage-form__footer'> <FormError type='backstage' - errors={[this.state.serverError, this.state.clientError]} + errors={[this.props.serverError, this.state.clientError]} /> <Link className='btn btn-sm' diff --git a/webapp/components/integrations/components/add_outgoing_webhook.jsx b/webapp/components/integrations/components/add_outgoing_webhook.jsx deleted file mode 100644 index d7f338587..000000000 --- a/webapp/components/integrations/components/add_outgoing_webhook.jsx +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import {addOutgoingHook} from 'actions/integration_actions.jsx'; -import {browserHistory} from 'react-router/es6'; - -import AbstractOutgoingWebhook from './abstract_outgoing_webhook.jsx'; - -export default class AddOutgoingWebhook extends AbstractOutgoingWebhook { - performAction(hook) { - addOutgoingHook( - hook, - (data) => { - browserHistory.push(`/${this.props.team.name}/integrations/confirm?type=outgoing_webhooks&id=${data.id}`); - }, - (err) => { - this.setState({ - saving: false, - serverError: err.message - }); - } - ); - } - - header() { - return {id: 'integrations.add', defaultMessage: 'Add'}; - } - - footer() { - return {id: 'add_outgoing_webhook.save', defaultMessage: 'Save'}; - } - - renderExtra() { - return ''; - } -} diff --git a/webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx b/webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx new file mode 100644 index 000000000..41ab8a073 --- /dev/null +++ b/webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx @@ -0,0 +1,69 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import AbstractOutgoingWebhook from 'components/integrations/components/abstract_outgoing_webhook.jsx'; + +import React from 'react'; +import {browserHistory} from 'react-router/es6'; +import PropTypes from 'prop-types'; + +const HEADER = {id: 'integrations.add', defaultMessage: 'Add'}; +const FOOTER = {id: 'add_outgoing_webhook.save', defaultMessage: 'Save'}; + +export default class AddOutgoingWebhook extends React.PureComponent { + static propTypes = { + + /** + * The current team + */ + team: PropTypes.object.isRequired, + + /** + * The request state for createOutgoingHook action. Contains status and error + */ + createOutgoingHookRequest: PropTypes.object.isRequired, + + actions: PropTypes.shape({ + + /** + * The function to call to add a new outgoing webhook + */ + createOutgoingHook: PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.state = { + serverError: '' + }; + } + + addOutgoingHook = async (hook) => { + this.setState({serverError: ''}); + + const data = await this.props.actions.createOutgoingHook(hook); + if (data) { + browserHistory.push(`/${this.props.team.name}/integrations/confirm?type=outgoing_webhooks&id=${data.id}`); + return; + } + + if (this.props.createOutgoingHookRequest.error) { + this.setState({serverError: this.props.createOutgoingHookRequest.error.message}); + } + } + + render() { + return ( + <AbstractOutgoingWebhook + team={this.props.team} + header={HEADER} + footer={FOOTER} + renderExtra={''} + action={this.addOutgoingHook} + serverError={this.state.serverError} + /> + ); + } +} diff --git a/webapp/components/integrations/components/add_outgoing_webhook/index.js b/webapp/components/integrations/components/add_outgoing_webhook/index.js new file mode 100644 index 000000000..f930ac81f --- /dev/null +++ b/webapp/components/integrations/components/add_outgoing_webhook/index.js @@ -0,0 +1,25 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {createOutgoingHook} from 'mattermost-redux/actions/integrations'; + +import AddOutgoingWebhook from './add_outgoing_webhook.jsx'; + +function mapStateToProps(state, ownProps) { + return { + ...ownProps, + createOutgoingHookRequest: state.requests.integrations.createOutgoingHook + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + createOutgoingHook + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AddOutgoingWebhook); diff --git a/webapp/components/integrations/components/edit_outgoing_webhook.jsx b/webapp/components/integrations/components/edit_outgoing_webhook.jsx deleted file mode 100644 index 2b6776b28..000000000 --- a/webapp/components/integrations/components/edit_outgoing_webhook.jsx +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; - -import {browserHistory} from 'react-router/es6'; -import IntegrationStore from 'stores/integration_store.jsx'; -import {loadOutgoingHooks, updateOutgoingHook} 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() { - 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} - confirmButtonText={confirmButton} - show={this.state.showConfirmModal} - onConfirm={this.handleUpdate} - onCancel={this.confirmModalDismissed} - /> - ); - } -} diff --git a/webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx b/webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx new file mode 100644 index 000000000..9b2dbff0a --- /dev/null +++ b/webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx @@ -0,0 +1,169 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import AbstractOutgoingWebhook from 'components/integrations/components/abstract_outgoing_webhook.jsx'; +import ConfirmModal from 'components/confirm_modal.jsx'; +import LoadingScreen from 'components/loading_screen.jsx'; + +import React from 'react'; +import PropTypes from 'prop-types'; +import {browserHistory} from 'react-router/es6'; +import {FormattedMessage} from 'react-intl'; + +const HEADER = {id: 'integrations.edit', defaultMessage: 'Edit'}; +const FOOTER = {id: 'update_outgoing_webhook.update', defaultMessage: 'Update'}; + +export default class EditOutgoingWebhook extends React.PureComponent { + static propTypes = { + + /** + * The current team + */ + team: PropTypes.object.isRequired, + + /** + * The outgoing webhook to edit + */ + hook: PropTypes.object, + + /** + * The id of the outgoing webhook to edit + */ + hookId: PropTypes.string.isRequired, + + /** + * The request state for updateOutgoingHook action. Contains status and error + */ + updateOutgoingHookRequest: PropTypes.object.isRequired, + + actions: PropTypes.shape({ + + /** + * The function to call to update an outgoing webhook + */ + updateOutgoingHook: PropTypes.func.isRequired, + + /** + * The function to call to get an outgoing webhook + */ + getOutgoingHook: PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.state = { + showConfirmModal: false, + serverError: '' + }; + } + + componentDidMount() { + if (window.mm_config.EnableOutgoingWebhooks === 'true') { + this.props.actions.getOutgoingHook(this.props.hookId); + } + } + + editOutgoingHook = async (hook) => { + this.newHook = hook; + + if (this.props.hook.id) { + hook.id = this.props.hook.id; + } + + if (this.props.hook.token) { + hook.token = this.props.hook.token; + } + + const triggerWordsSame = (this.props.hook.trigger_words.length === hook.trigger_words.length) && + this.props.hook.trigger_words.every((v, i) => v === hook.trigger_words[i]); + + const callbackUrlsSame = (this.props.hook.callback_urls.length === hook.callback_urls.length) && + this.props.hook.callback_urls.every((v, i) => v === hook.callback_urls[i]); + + if (this.props.hook.content_type !== hook.content_type || + !triggerWordsSame || !callbackUrlsSame) { + this.handleConfirmModal(); + } else { + await this.submitHook(); + } + } + + handleConfirmModal = () => { + this.setState({showConfirmModal: true}); + } + + confirmModalDismissed = () => { + this.setState({showConfirmModal: false}); + } + + submitHook = async () => { + this.setState({serverError: ''}); + + const data = await this.props.actions.updateOutgoingHook(this.newHook); + + if (data) { + browserHistory.push(`/${this.props.team.name}/integrations/outgoing_webhooks`); + return; + } + + this.setState({showConfirmModal: false}); + + if (this.props.updateOutgoingHookRequest.error) { + this.setState({serverError: this.props.updateOutgoingHookRequest.error.message}); + } + } + + 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} + confirmButtonText={confirmButton} + show={this.state.showConfirmModal} + onConfirm={this.submitHook} + onCancel={this.confirmModalDismissed} + /> + ); + } + + render() { + if (!this.props.hook) { + return <LoadingScreen/>; + } + + return ( + <AbstractOutgoingWebhook + team={this.props.team} + header={HEADER} + footer={FOOTER} + renderExtra={this.renderExtra()} + action={this.editOutgoingHook} + serverError={this.state.serverError} + initialHook={this.props.hook} + /> + ); + } +} diff --git a/webapp/components/integrations/components/edit_outgoing_webhook/index.js b/webapp/components/integrations/components/edit_outgoing_webhook/index.js new file mode 100644 index 000000000..a526ac76c --- /dev/null +++ b/webapp/components/integrations/components/edit_outgoing_webhook/index.js @@ -0,0 +1,30 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {updateOutgoingHook, getOutgoingHook} from 'mattermost-redux/actions/integrations'; + +import EditOutgoingWebhook from './edit_outgoing_webhook.jsx'; + +function mapStateToProps(state, ownProps) { + const hookId = ownProps.location.query.id; + + return { + ...ownProps, + hookId, + hook: state.entities.integrations.outgoingHooks[hookId], + updateOutgoingHookRequest: state.requests.integrations.createOutgoingHook + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + updateOutgoingHook, + getOutgoingHook + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(EditOutgoingWebhook); diff --git a/webapp/routes/route_integrations.jsx b/webapp/routes/route_integrations.jsx index dd3ebe663..37b33ed40 100644 --- a/webapp/routes/route_integrations.jsx +++ b/webapp/routes/route_integrations.jsx @@ -47,13 +47,13 @@ export default { { path: 'add', getComponents: (location, callback) => { - System.import('components/integrations/components/add_outgoing_webhook.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/add_outgoing_webhook').then(RouteUtils.importComponentSuccess(callback)); } }, { path: 'edit', getComponents: (location, callback) => { - System.import('components/integrations/components/edit_outgoing_webhook.jsx').then(RouteUtils.importComponentSuccess(callback)); + System.import('components/integrations/components/edit_outgoing_webhook').then(RouteUtils.importComponentSuccess(callback)); } } ] diff --git a/webapp/tests/components/integrations/__snapshots__/add_outgoing_hook.test.jsx.snap b/webapp/tests/components/integrations/__snapshots__/add_outgoing_hook.test.jsx.snap new file mode 100644 index 000000000..a55f5db5e --- /dev/null +++ b/webapp/tests/components/integrations/__snapshots__/add_outgoing_hook.test.jsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/integrations/AddOutgoingWebhook should match snapshot 1`] = ` +<AbstractOutgoingWebhook + action={[Function]} + footer={ + Object { + "defaultMessage": "Save", + "id": "add_outgoing_webhook.save", + } + } + header={ + Object { + "defaultMessage": "Add", + "id": "integrations.add", + } + } + renderExtra="" + serverError="" + team={ + Object { + "id": "testteamid", + "name": "test", + } + } +/> +`; diff --git a/webapp/tests/components/integrations/__snapshots__/edit_outgoing_hook.test.jsx.snap b/webapp/tests/components/integrations/__snapshots__/edit_outgoing_hook.test.jsx.snap new file mode 100644 index 000000000..d7656b08f --- /dev/null +++ b/webapp/tests/components/integrations/__snapshots__/edit_outgoing_hook.test.jsx.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/integrations/EditOutgoingWebhook should match snapshot 1`] = ` +<LoadingScreen + position="relative" +/> +`; diff --git a/webapp/tests/components/integrations/add_outgoing_hook.test.jsx b/webapp/tests/components/integrations/add_outgoing_hook.test.jsx new file mode 100644 index 000000000..0c92a7c83 --- /dev/null +++ b/webapp/tests/components/integrations/add_outgoing_hook.test.jsx @@ -0,0 +1,29 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import AddOutgoingWebhook from 'components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx'; + +describe('components/integrations/AddOutgoingWebhook', () => { + test('should match snapshot', () => { + function emptyFunction() {} //eslint-disable-line no-empty-function + const teamId = 'testteamid'; + + const wrapper = shallow( + <AddOutgoingWebhook + team={{ + id: teamId, + name: 'test' + }} + createOutgoingHookRequest={{ + status: 'not_started', + error: null + }} + actions={{createOutgoingHook: emptyFunction}} + /> + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/webapp/tests/components/integrations/edit_outgoing_hook.test.jsx b/webapp/tests/components/integrations/edit_outgoing_hook.test.jsx new file mode 100644 index 000000000..c2a5020a6 --- /dev/null +++ b/webapp/tests/components/integrations/edit_outgoing_hook.test.jsx @@ -0,0 +1,31 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import EditOutgoingWebhook from 'components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx'; + +describe('components/integrations/EditOutgoingWebhook', () => { + test('should match snapshot', () => { + function emptyFunction() {} //eslint-disable-line no-empty-function + const teamId = 'testteamid'; + + const wrapper = shallow( + <EditOutgoingWebhook + team={{ + id: teamId, + name: 'test' + }} + hookId={'somehookid'} + updateOutgoingHookRequest={{ + status: 'not_started', + error: null + }} + actions={{updateOutgoingHook: emptyFunction, getOutgoingHook: emptyFunction}} + /> + ); + expect(wrapper).toMatchSnapshot(); + }); +}); + |