summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2017-07-10 17:43:47 -0400
committerGitHub <noreply@github.com>2017-07-10 17:43:47 -0400
commit0cc60abf6a33dca0d8317481f83d0eb2771f43a1 (patch)
tree8e56bd479017bacae44e859c743c16080839240d
parent6330f7f6377a4a8af956399ed299300f3d98a7a6 (diff)
downloadchat-0cc60abf6a33dca0d8317481f83d0eb2771f43a1.tar.gz
chat-0cc60abf6a33dca0d8317481f83d0eb2771f43a1.tar.bz2
chat-0cc60abf6a33dca0d8317481f83d0eb2771f43a1.zip
Migrate add and edit outgoing webhook components to redux (#6818)
-rw-r--r--model/outgoing_webhook.go31
-rw-r--r--webapp/components/integrations/components/abstract_outgoing_webhook.jsx131
-rw-r--r--webapp/components/integrations/components/add_outgoing_webhook.jsx36
-rw-r--r--webapp/components/integrations/components/add_outgoing_webhook/add_outgoing_webhook.jsx69
-rw-r--r--webapp/components/integrations/components/add_outgoing_webhook/index.js25
-rw-r--r--webapp/components/integrations/components/edit_outgoing_webhook.jsx188
-rw-r--r--webapp/components/integrations/components/edit_outgoing_webhook/edit_outgoing_webhook.jsx169
-rw-r--r--webapp/components/integrations/components/edit_outgoing_webhook/index.js30
-rw-r--r--webapp/routes/route_integrations.jsx4
-rw-r--r--webapp/tests/components/integrations/__snapshots__/add_outgoing_hook.test.jsx.snap27
-rw-r--r--webapp/tests/components/integrations/__snapshots__/edit_outgoing_hook.test.jsx.snap7
-rw-r--r--webapp/tests/components/integrations/add_outgoing_hook.test.jsx29
-rw-r--r--webapp/tests/components/integrations/edit_outgoing_hook.test.jsx31
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();
+ });
+});
+