From dc2f2a800105b77e665ec2a00c6290f35b1a2ba3 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 5 Jul 2016 11:58:18 -0400 Subject: PLT-3145 Custom Emojis (#3381) * Reorganized Backstage code to use a view controller and separated it from integrations code * Renamed InstalledIntegrations component to BackstageList * Added EmojiList page * Added AddEmoji page * Added custom emoji to autocomplete and text formatter * Moved system emoji to EmojiStore * Stopped trying to get emoji before logging in * Rerender posts when emojis change * Fixed submit handler on backstage pages to properly support enter * Removed debugging code * Updated javascript driver * Fixed unit tests * Fixed backstage routes * Added clientside validation to prevent users from creating an emoji with the same name as a system one * Fixed AddEmoji page to properly redirect when an emoji is created successfully * Fixed updating emoji list when an emoji is deleted * Added type prop to BackstageList to properly support using a table for the list * Added help text to EmojiList * Fixed backstage on smaller screen sizes * Disable custom emoji by default * Improved restrictions on creating emojis * Fixed non-admin users seeing the option to delete each other's emojis * Fixing gofmt * Fixed emoji unit tests * Fixed trying to get emoji from the server when it's disabled --- Makefile | 5 +- api/emoji.go | 6 +- api/emoji_test.go | 31 +- config/config.json | 8 +- einterfaces/emoji.go | 22 + i18n/en.json | 10 +- model/config.go | 5 +- .../admin_console/custom_emoji_settings.jsx | 51 +- webapp/components/backstage/add_command.jsx | 558 -- .../components/backstage/add_incoming_webhook.jsx | 208 - .../components/backstage/add_outgoing_webhook.jsx | 341 - webapp/components/backstage/backstage_category.jsx | 72 - .../components/backstage/backstage_controller.jsx | 71 + webapp/components/backstage/backstage_header.jsx | 39 - webapp/components/backstage/backstage_navbar.jsx | 65 - webapp/components/backstage/backstage_section.jsx | 79 - webapp/components/backstage/backstage_sidebar.jsx | 80 - .../backstage/components/backstage_category.jsx | 73 + .../backstage/components/backstage_header.jsx | 39 + .../backstage/components/backstage_list.jsx | 108 + .../backstage/components/backstage_navbar.jsx | 41 + .../backstage/components/backstage_section.jsx | 79 + .../backstage/components/backstage_sidebar.jsx | 125 + webapp/components/backstage/installed_command.jsx | 146 - webapp/components/backstage/installed_commands.jsx | 100 - .../backstage/installed_incoming_webhook.jsx | 131 - .../backstage/installed_incoming_webhooks.jsx | 94 - .../backstage/installed_integrations.jsx | 101 - .../backstage/installed_outgoing_webhook.jsx | 199 - .../backstage/installed_outgoing_webhooks.jsx | 100 - webapp/components/backstage/integration_option.jsx | 39 - webapp/components/backstage/integrations.jsx | 99 - webapp/components/emoji/components/add_emoji.jsx | 307 + webapp/components/emoji/components/emoji_list.jsx | 218 + .../emoji/components/emoji_list_item.jsx | 118 + .../integrations/components/add_command.jsx | 567 ++ .../components/add_incoming_webhook.jsx | 216 + .../components/add_outgoing_webhook.jsx | 349 + .../integrations/components/installed_command.jsx | 147 + .../integrations/components/installed_commands.jsx | 107 + .../components/installed_incoming_webhook.jsx | 132 + .../components/installed_incoming_webhooks.jsx | 101 + .../components/installed_outgoing_webhook.jsx | 200 + .../components/installed_outgoing_webhooks.jsx | 107 + .../integrations/components/integration_option.jsx | 39 + .../integrations/components/integrations.jsx | 104 + webapp/components/logged_in.jsx | 7 +- webapp/components/navbar_dropdown.jsx | 20 +- webapp/components/post_view/components/post.jsx | 8 +- .../components/post_view/components/post_body.jsx | 9 +- .../components/post_body_additional_content.jsx | 3 + .../components/post_view/components/post_list.jsx | 4 +- .../post_view/post_focus_view_controller.jsx | 14 +- .../components/post_view/post_view_controller.jsx | 14 +- webapp/components/root.jsx | 3 - webapp/components/suggestion/emoticon_provider.jsx | 21 +- webapp/i18n/en.json | 35 +- webapp/package.json | 2 +- webapp/routes/route_emoji.jsx | 24 + webapp/routes/route_integrations.jsx | 140 +- webapp/routes/route_team.jsx | 85 +- webapp/sass/responsive/_mobile.scss | 1 - webapp/sass/routes/_backstage.scss | 196 +- webapp/stores/emoji_store.jsx | 135 + webapp/stores/team_store.jsx | 7 +- webapp/tests/emoticons.test.jsx | 15 +- webapp/utils/async_client.jsx | 85 + webapp/utils/constants.jsx | 5 + webapp/utils/emoji.json | 8334 +------------------- webapp/utils/emoticons.jsx | 121 +- webapp/utils/text_formatting.jsx | 11 +- webapp/utils/utils.jsx | 5 +- 72 files changed, 3959 insertions(+), 11182 deletions(-) create mode 100644 einterfaces/emoji.go delete mode 100644 webapp/components/backstage/add_command.jsx delete mode 100644 webapp/components/backstage/add_incoming_webhook.jsx delete mode 100644 webapp/components/backstage/add_outgoing_webhook.jsx delete mode 100644 webapp/components/backstage/backstage_category.jsx create mode 100644 webapp/components/backstage/backstage_controller.jsx delete mode 100644 webapp/components/backstage/backstage_header.jsx delete mode 100644 webapp/components/backstage/backstage_navbar.jsx delete mode 100644 webapp/components/backstage/backstage_section.jsx delete mode 100644 webapp/components/backstage/backstage_sidebar.jsx create mode 100644 webapp/components/backstage/components/backstage_category.jsx create mode 100644 webapp/components/backstage/components/backstage_header.jsx create mode 100644 webapp/components/backstage/components/backstage_list.jsx create mode 100644 webapp/components/backstage/components/backstage_navbar.jsx create mode 100644 webapp/components/backstage/components/backstage_section.jsx create mode 100644 webapp/components/backstage/components/backstage_sidebar.jsx delete mode 100644 webapp/components/backstage/installed_command.jsx delete mode 100644 webapp/components/backstage/installed_commands.jsx delete mode 100644 webapp/components/backstage/installed_incoming_webhook.jsx delete mode 100644 webapp/components/backstage/installed_incoming_webhooks.jsx delete mode 100644 webapp/components/backstage/installed_integrations.jsx delete mode 100644 webapp/components/backstage/installed_outgoing_webhook.jsx delete mode 100644 webapp/components/backstage/installed_outgoing_webhooks.jsx delete mode 100644 webapp/components/backstage/integration_option.jsx delete mode 100644 webapp/components/backstage/integrations.jsx create mode 100644 webapp/components/emoji/components/add_emoji.jsx create mode 100644 webapp/components/emoji/components/emoji_list.jsx create mode 100644 webapp/components/emoji/components/emoji_list_item.jsx create mode 100644 webapp/components/integrations/components/add_command.jsx create mode 100644 webapp/components/integrations/components/add_incoming_webhook.jsx create mode 100644 webapp/components/integrations/components/add_outgoing_webhook.jsx create mode 100644 webapp/components/integrations/components/installed_command.jsx create mode 100644 webapp/components/integrations/components/installed_commands.jsx create mode 100644 webapp/components/integrations/components/installed_incoming_webhook.jsx create mode 100644 webapp/components/integrations/components/installed_incoming_webhooks.jsx create mode 100644 webapp/components/integrations/components/installed_outgoing_webhook.jsx create mode 100644 webapp/components/integrations/components/installed_outgoing_webhooks.jsx create mode 100644 webapp/components/integrations/components/integration_option.jsx create mode 100644 webapp/components/integrations/components/integrations.jsx create mode 100644 webapp/routes/route_emoji.jsx create mode 100644 webapp/stores/emoji_store.jsx diff --git a/Makefile b/Makefile index 368b3234b..131cef571 100644 --- a/Makefile +++ b/Makefile @@ -180,12 +180,15 @@ ifeq ($(BUILD_ENTERPRISE_READY),true) $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/ldap && ./ldap.test -test.v -test.timeout=120s -test.coverprofile=cldap.out || exit 1 $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/compliance && ./compliance.test -test.v -test.timeout=120s -test.coverprofile=ccompliance.out || exit 1 + $(GO) test $(GOFLAGS) -run=$(TESTS) -covermode=count -c ./enterprise/emoji && ./emoji.test -test.v -test.timeout=120s -test.coverprofile=cemoji.out || exit 1 tail -n +2 cldap.out >> ecover.out tail -n +2 ccompliance.out >> ecover.out - rm -f cldap.out ccompliance.out + tail -n +2 cemoji.out >> ecover.out + rm -f cldap.out ccompliance.out cemoji.out rm -r ldap.test rm -r compliance.test + rm -r emoji.test endif internal-test-web-client: start-docker prepare-enterprise diff --git a/api/emoji.go b/api/emoji.go index 24989924a..d84996230 100644 --- a/api/emoji.go +++ b/api/emoji.go @@ -16,6 +16,7 @@ import ( l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" + "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" ) @@ -32,7 +33,7 @@ func InitEmoji() { BaseRoutes.Emoji.Handle("/list", ApiUserRequired(getEmoji)).Methods("GET") BaseRoutes.Emoji.Handle("/create", ApiUserRequired(createEmoji)).Methods("POST") BaseRoutes.Emoji.Handle("/delete", ApiUserRequired(deleteEmoji)).Methods("POST") - BaseRoutes.Emoji.Handle("/{id:[A-Za-z0-9_]+}", ApiUserRequired(getEmojiImage)).Methods("GET") + BaseRoutes.Emoji.Handle("/{id:[A-Za-z0-9_]+}", ApiUserRequiredTrustRequester(getEmojiImage)).Methods("GET") } func getEmoji(c *Context, w http.ResponseWriter, r *http.Request) { @@ -58,7 +59,8 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !(*utils.Cfg.ServiceSettings.RestrictCustomEmojiCreation == model.RESTRICT_EMOJI_CREATION_ALL || c.IsSystemAdmin()) { + if emojiInterface := einterfaces.GetEmojiInterface(); emojiInterface != nil && + !emojiInterface.CanUserCreateEmoji(c.Session.Roles, c.Session.TeamMembers) { c.Err = model.NewLocAppError("createEmoji", "api.emoji.create.permissions.app_error", nil, "user_id="+c.Session.UserId) c.Err.StatusCode = http.StatusUnauthorized return diff --git a/api/emoji_test.go b/api/emoji_test.go index 26dbe9323..fb23cc439 100644 --- a/api/emoji_test.go +++ b/api/emoji_test.go @@ -22,6 +22,12 @@ func TestGetEmoji(t *testing.T) { th := Setup().InitBasic() Client := th.BasicClient + EnableCustomEmoji := *utils.Cfg.ServiceSettings.EnableCustomEmoji + defer func() { + *utils.Cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji + }() + *utils.Cfg.ServiceSettings.EnableCustomEmoji = true + emojis := []*model.Emoji{ { CreatorId: model.NewId(), @@ -95,13 +101,10 @@ func TestCreateEmoji(t *testing.T) { Client := th.BasicClient EnableCustomEmoji := *utils.Cfg.ServiceSettings.EnableCustomEmoji - RestrictCustomEmojiCreation := *utils.Cfg.ServiceSettings.RestrictCustomEmojiCreation defer func() { *utils.Cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji - *utils.Cfg.ServiceSettings.RestrictCustomEmojiCreation = RestrictCustomEmojiCreation }() *utils.Cfg.ServiceSettings.EnableCustomEmoji = false - *utils.Cfg.ServiceSettings.RestrictCustomEmojiCreation = model.RESTRICT_EMOJI_CREATION_ALL emoji := &model.Emoji{ CreatorId: th.BasicUser.Id, @@ -213,28 +216,6 @@ func TestCreateEmoji(t *testing.T) { if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err == nil { t.Fatal("shouldn't be able to create an emoji as another user") } - - *utils.Cfg.ServiceSettings.RestrictCustomEmojiCreation = model.RESTRICT_EMOJI_CREATION_ADMIN - - // try to create an emoji when only system admins are allowed to create them - emoji = &model.Emoji{ - CreatorId: th.BasicUser.Id, - Name: model.NewId(), - } - if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err == nil { - t.Fatal("shouldn't be able to create an emoji when not a system admin") - } - - emoji = &model.Emoji{ - CreatorId: th.SystemAdminUser.Id, - Name: model.NewId(), - } - if emojiResult, err := th.SystemAdminClient.CreateEmoji(emoji, createTestPng(t, 10, 10), "image.png"); err != nil { - t.Fatal(err) - } else { - emoji = emojiResult - } - th.SystemAdminClient.MustGeneric(th.SystemAdminClient.DeleteEmoji(emoji.Id)) } func TestDeleteEmoji(t *testing.T) { diff --git a/config/config.json b/config/config.json index eeb75d0c1..fb325248d 100644 --- a/config/config.json +++ b/config/config.json @@ -5,9 +5,9 @@ "SegmentDeveloperKey": "", "GoogleDeveloperKey": "", "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": false, - "EnableOutgoingWebhooks": false, - "EnableCommands": false, + "EnableIncomingWebhooks": true, + "EnableOutgoingWebhooks": true, + "EnableCommands": true, "EnableOnlyAdminIntegrations": true, "EnablePostUsernameOverride": false, "EnablePostIconOverride": false, @@ -24,7 +24,7 @@ "WebsocketSecurePort": 443, "WebsocketPort": 80, "WebserverMode": "regular", - "EnableCustomEmoji": true, + "EnableCustomEmoji": false, "RestrictCustomEmojiCreation": "all" }, "TeamSettings": { diff --git a/einterfaces/emoji.go b/einterfaces/emoji.go new file mode 100644 index 000000000..f276f6a32 --- /dev/null +++ b/einterfaces/emoji.go @@ -0,0 +1,22 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package einterfaces + +import ( + "github.com/mattermost/platform/model" +) + +type EmojiInterface interface { + CanUserCreateEmoji(string, []*model.TeamMember) bool +} + +var theEmojiInterface EmojiInterface + +func RegisterEmojiInterface(newInterface EmojiInterface) { + theEmojiInterface = newInterface +} + +func GetEmojiInterface() EmojiInterface { + return theEmojiInterface +} diff --git a/i18n/en.json b/i18n/en.json index 3d433dae9..f76c7f637 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -581,7 +581,7 @@ }, { "id": "api.emoji.create.parse.app_error", - "translation": "Unable to create emoji. Image exceeds maximum file size." + "translation": "Unable to create emoji. Could not understand request." }, { "id": "api.emoji.create.permissions.app_error", @@ -589,7 +589,7 @@ }, { "id": "api.emoji.create.too_large.app_error", - "translation": "Unable to create emoji. Could not understand request." + "translation": "Unable to create emoji. Image must be less than 64 KB in size." }, { "id": "api.emoji.delete.permissions.app_error", @@ -621,7 +621,7 @@ }, { "id": "api.emoji.upload.large_image.app_error", - "translation": "Unable to create emoji. Image exceeds maximum dimensions." + "translation": "Unable to create emoji. Image must be at most 128 by 128 pixels." }, { "id": "api.export.json.app_error", @@ -2079,6 +2079,10 @@ "id": "ent.compliance.run_started.info", "translation": "Compliance export started for job '{{.JobName}}' at '{{.FilePath}}'" }, + { + "id": "ent.emoji.licence_disable.app_error", + "translation": "Custom emoji restrictions disabled by current license. Please contact your system administrator about upgrading your enterprise license." + }, { "id": "ent.ldap.do_login.bind_admin_user.app_error", "translation": "Unable to bind to LDAP server. Check BindUsername and BindPassword." diff --git a/model/config.go b/model/config.go index e71a58a21..a8c63b1eb 100644 --- a/model/config.go +++ b/model/config.go @@ -38,8 +38,9 @@ const ( FAKE_SETTING = "********************************" - RESTRICT_EMOJI_CREATION_ALL = "all" - RESTRICT_EMOJI_CREATION_ADMIN = "system_admin" + RESTRICT_EMOJI_CREATION_ALL = "all" + RESTRICT_EMOJI_CREATION_ADMIN = "admin" + RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN = "system_admin" ) type ServiceSettings struct { diff --git a/webapp/components/admin_console/custom_emoji_settings.jsx b/webapp/components/admin_console/custom_emoji_settings.jsx index 332c7b216..738afa3cd 100644 --- a/webapp/components/admin_console/custom_emoji_settings.jsx +++ b/webapp/components/admin_console/custom_emoji_settings.jsx @@ -27,7 +27,10 @@ export default class CustomEmojiSettings extends AdminSettings { getConfigFromState(config) { config.ServiceSettings.EnableCustomEmoji = this.state.enableCustomEmoji; - config.ServiceSettings.RestrictCustomEmojiCreation = this.state.restrictCustomEmojiCreation; + + if (global.window.mm_license.IsLicensed === 'true') { + config.ServiceSettings.RestrictCustomEmojiCreation = this.state.restrictCustomEmojiCreation; + } return config; } @@ -44,29 +47,14 @@ export default class CustomEmojiSettings extends AdminSettings { } renderSettings() { - return ( - - - } - helpText={ - - } - value={this.state.enableCustomEmoji} - onChange={this.handleChange} - /> + let restrictSetting = null; + if (global.window.mm_license.IsLicensed === 'true') { + restrictSetting = ( + ); + } + + return ( + + + } + helpText={ + + } + value={this.state.enableCustomEmoji} + onChange={this.handleChange} + /> + {restrictSetting} ); } diff --git a/webapp/components/backstage/add_command.jsx b/webapp/components/backstage/add_command.jsx deleted file mode 100644 index 91af0416b..000000000 --- a/webapp/components/backstage/add_command.jsx +++ /dev/null @@ -1,558 +0,0 @@ -// 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 * as Utils from 'utils/utils.jsx'; - -import BackstageHeader from './backstage_header.jsx'; -import {FormattedMessage} from 'react-intl'; -import FormError from 'components/form_error.jsx'; -import {browserHistory, Link} from 'react-router/es6'; -import SpinnerButton from 'components/spinner_button.jsx'; -import Constants from 'utils/constants.jsx'; - -const REQUEST_POST = 'P'; -const REQUEST_GET = 'G'; - -export default class AddCommand extends React.Component { - constructor(props) { - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); - - this.updateDisplayName = this.updateDisplayName.bind(this); - this.updateDescription = this.updateDescription.bind(this); - this.updateTrigger = this.updateTrigger.bind(this); - this.updateUrl = this.updateUrl.bind(this); - this.updateMethod = this.updateMethod.bind(this); - this.updateUsername = this.updateUsername.bind(this); - this.updateIconUrl = this.updateIconUrl.bind(this); - this.updateAutocomplete = this.updateAutocomplete.bind(this); - this.updateAutocompleteHint = this.updateAutocompleteHint.bind(this); - this.updateAutocompleteDescription = this.updateAutocompleteDescription.bind(this); - - this.state = { - displayName: '', - description: '', - trigger: '', - url: '', - method: REQUEST_POST, - username: '', - iconUrl: '', - autocomplete: false, - autocompleteHint: '', - autocompleteDescription: '', - saving: false, - serverError: '', - clientError: null - }; - } - - handleSubmit(e) { - e.preventDefault(); - - if (this.state.saving) { - return; - } - - this.setState({ - saving: true, - serverError: '', - clientError: '' - }); - - const command = { - display_name: this.state.displayName, - description: this.state.description, - trigger: this.state.trigger.trim(), - url: this.state.url.trim(), - method: this.state.method, - username: this.state.username, - icon_url: this.state.iconUrl, - auto_complete: this.state.autocomplete - }; - - if (command.auto_complete) { - command.auto_complete_desc = this.state.autocompleteDescription; - command.auto_complete_hint = this.state.autocompleteHint; - } - - if (!command.trigger) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - if (command.trigger.indexOf('/') === 0) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - if (command.trigger.indexOf(' ') !== -1) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - return; - } - - if (command.trigger.length < Constants.MIN_TRIGGER_LENGTH || command.trigger.length > Constants.MAX_TRIGGER_LENGTH) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - if (!command.url) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - AsyncClient.addCommand( - command, - () => { - browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/commands'); - }, - (err) => { - this.setState({ - saving: false, - serverError: err.message - }); - } - ); - } - - updateDisplayName(e) { - this.setState({ - displayName: e.target.value - }); - } - - updateDescription(e) { - this.setState({ - description: e.target.value - }); - } - - updateTrigger(e) { - this.setState({ - trigger: e.target.value - }); - } - - updateUrl(e) { - this.setState({ - url: e.target.value - }); - } - - updateMethod(e) { - this.setState({ - method: e.target.value - }); - } - - updateUsername(e) { - this.setState({ - username: e.target.value - }); - } - - updateIconUrl(e) { - this.setState({ - iconUrl: e.target.value - }); - } - - updateAutocomplete(e) { - this.setState({ - autocomplete: e.target.checked - }); - } - - updateAutocompleteHint(e) { - this.setState({ - autocompleteHint: e.target.value - }); - } - - updateAutocompleteDescription(e) { - this.setState({ - autocompleteDescription: e.target.value - }); - } - - render() { - let autocompleteFields = null; - if (this.state.autocomplete) { - autocompleteFields = [( -
- -
- -
- -
-
-
- ), - ( -
- -
- -
- -
-
-
- )]; - } - - return ( -
- - - - - - -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
- -
-
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
-
-
- - -
-
- -
-
-
- {autocompleteFields} -
- - - - - - - -
-
-
-
- ); - } -} diff --git a/webapp/components/backstage/add_incoming_webhook.jsx b/webapp/components/backstage/add_incoming_webhook.jsx deleted file mode 100644 index 528f03377..000000000 --- a/webapp/components/backstage/add_incoming_webhook.jsx +++ /dev/null @@ -1,208 +0,0 @@ -// 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 * as Utils from 'utils/utils.jsx'; - -import BackstageHeader from './backstage_header.jsx'; -import ChannelSelect from 'components/channel_select.jsx'; -import {FormattedMessage} from 'react-intl'; -import FormError from 'components/form_error.jsx'; -import {browserHistory, Link} from 'react-router/es6'; -import SpinnerButton from 'components/spinner_button.jsx'; - -export default class AddIncomingWebhook extends React.Component { - constructor(props) { - super(props); - - this.handleSubmit = this.handleSubmit.bind(this); - - this.updateDisplayName = this.updateDisplayName.bind(this); - this.updateDescription = this.updateDescription.bind(this); - this.updateChannelId = this.updateChannelId.bind(this); - - this.state = { - displayName: '', - description: '', - channelId: '', - saving: false, - serverError: '', - clientError: null - }; - } - - handleSubmit(e) { - e.preventDefault(); - - if (this.state.saving) { - return; - } - - this.setState({ - saving: true, - serverError: '', - clientError: '' - }); - - if (!this.state.channelId) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - const hook = { - channel_id: this.state.channelId, - display_name: this.state.displayName, - description: this.state.description - }; - - AsyncClient.addIncomingHook( - hook, - () => { - browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/incoming_webhooks'); - }, - (err) => { - this.setState({ - saving: false, - serverError: err.message - }); - } - ); - } - - updateDisplayName(e) { - this.setState({ - displayName: e.target.value - }); - } - - updateDescription(e) { - this.setState({ - description: e.target.value - }); - } - - updateChannelId(e) { - this.setState({ - channelId: e.target.value - }); - } - - render() { - return ( -
- - - - - - -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- - - - - - - -
-
-
-
- ); - } -} diff --git a/webapp/components/backstage/add_outgoing_webhook.jsx b/webapp/components/backstage/add_outgoing_webhook.jsx deleted file mode 100644 index 5f9d96249..000000000 --- a/webapp/components/backstage/add_outgoing_webhook.jsx +++ /dev/null @@ -1,341 +0,0 @@ -// 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 * as Utils from 'utils/utils.jsx'; - -import BackstageHeader from './backstage_header.jsx'; -import ChannelSelect from 'components/channel_select.jsx'; -import {FormattedMessage} from 'react-intl'; -import FormError from 'components/form_error.jsx'; -import {browserHistory, Link} from 'react-router/es6'; -import SpinnerButton from 'components/spinner_button.jsx'; - -export default class AddOutgoingWebhook extends React.Component { - 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.updateCallbackUrls = this.updateCallbackUrls.bind(this); - - this.state = { - displayName: '', - description: '', - contentType: 'application/x-www-form-urlencoded', - channelId: '', - triggerWords: '', - callbackUrls: '', - saving: false, - serverError: '', - clientError: null - }; - } - - handleSubmit(e) { - e.preventDefault(); - - if (this.state.saving) { - return; - } - - this.setState({ - saving: true, - serverError: '', - clientError: '' - }); - - const triggerWords = []; - if (this.state.triggerWords) { - for (let triggerWord of this.state.triggerWords.split('\n')) { - triggerWord = triggerWord.trim(); - - if (triggerWord.length > 0) { - triggerWords.push(triggerWord); - } - } - } - - if (!this.state.channelId && triggerWords.length === 0) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - const callbackUrls = []; - for (let callbackUrl of this.state.callbackUrls.split('\n')) { - callbackUrl = callbackUrl.trim(); - - if (callbackUrl.length > 0) { - callbackUrls.push(callbackUrl); - } - } - - if (callbackUrls.length === 0) { - this.setState({ - saving: false, - clientError: ( - - ) - }); - - return; - } - - const hook = { - channel_id: this.state.channelId, - trigger_words: triggerWords, - callback_urls: callbackUrls, - display_name: this.state.displayName, - content_type: this.state.contentType, - description: this.state.description - }; - - AsyncClient.addOutgoingHook( - hook, - () => { - browserHistory.push('/' + Utils.getTeamNameFromUrl() + '/settings/integrations/outgoing_webhooks'); - }, - (err) => { - this.setState({ - saving: false, - serverError: err.message - }); - } - ); - } - - updateDisplayName(e) { - this.setState({ - displayName: e.target.value - }); - } - - updateDescription(e) { - this.setState({ - description: e.target.value - }); - } - - updateContentType(e) { - this.setState({ - contentType: e.target.value - }); - } - - updateChannelId(e) { - this.setState({ - channelId: e.target.value - }); - } - - updateTriggerWords(e) { - this.setState({ - triggerWords: e.target.value - }); - } - - updateCallbackUrls(e) { - this.setState({ - callbackUrls: e.target.value - }); - } - - render() { - const contentTypeOption1 = 'application/x-www-form-urlencoded'; - const contentTypeOption2 = 'application/json'; - return ( -
- - - - - - -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
-