From 0cc60abf6a33dca0d8317481f83d0eb2771f43a1 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Mon, 10 Jul 2017 17:43:47 -0400 Subject: Migrate add and edit outgoing webhook components to redux (#6818) --- .../components/abstract_outgoing_webhook.jsx | 131 ++++++++------ .../components/add_outgoing_webhook.jsx | 36 ---- .../add_outgoing_webhook/add_outgoing_webhook.jsx | 69 ++++++++ .../components/add_outgoing_webhook/index.js | 25 +++ .../components/edit_outgoing_webhook.jsx | 188 --------------------- .../edit_outgoing_webhook.jsx | 169 ++++++++++++++++++ .../components/edit_outgoing_webhook/index.js | 30 ++++ 7 files changed, 368 insertions(+), 280 deletions(-) delete mode 100644 webapp/components/integrations/components/add_outgoing_webhook.jsx create mode 100644 webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx create mode 100644 webapp/components/integrations/components/add_outgoing_webhook/index.js delete mode 100644 webapp/components/integrations/components/edit_outgoing_webhook.jsx create mode 100644 webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx create mode 100644 webapp/components/integrations/components/edit_outgoing_webhook/index.js (limited to 'webapp/components/integrations') 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 (
@@ -432,7 +451,7 @@ export default class AbstractOutgoingWebhook extends React.Component {
{ - 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 ( + + ); + } +} 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 = ( - - ); - - const confirmTitle = ( - - ); - - const confirmMessage = ( - - ); - - return ( - - ); - } -} 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 = ( + + ); + + const confirmTitle = ( + + ); + + const confirmMessage = ( + + ); + + return ( + + ); + } + + render() { + if (!this.props.hook) { + return ; + } + + return ( + + ); + } +} 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); -- cgit v1.2.3-1-g7c22