From 6ec24867bc75057fa58c11e80a6b28334473983b Mon Sep 17 00:00:00 2001 From: 94117nl Date: Mon, 31 Jul 2017 07:26:04 -0500 Subject: GH-6448 Migrate edit_command.jsx to be pure and use Redux (#6858) * Migrate edit_command.jsx to be pure and use Redux, add basic test * Update newCommand to reference modified command * Fix typo * Remove unnecessary re-renders --- .../integrations/components/edit_command.jsx | 732 --------------------- .../components/edit_command/edit_command.jsx | 727 ++++++++++++++++++++ .../integrations/components/edit_command/index.js | 31 + 3 files changed, 758 insertions(+), 732 deletions(-) delete mode 100644 webapp/components/integrations/components/edit_command.jsx create mode 100644 webapp/components/integrations/components/edit_command/edit_command.jsx create mode 100644 webapp/components/integrations/components/edit_command/index.js (limited to 'webapp/components/integrations') diff --git a/webapp/components/integrations/components/edit_command.jsx b/webapp/components/integrations/components/edit_command.jsx deleted file mode 100644 index 817eb7367..000000000 --- a/webapp/components/integrations/components/edit_command.jsx +++ /dev/null @@ -1,732 +0,0 @@ -// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -import React from 'react'; -import PropTypes from 'prop-types'; - -import IntegrationStore from 'stores/integration_store.jsx'; -import TeamStore from 'stores/team_store.jsx'; -import * as Utils from 'utils/utils.jsx'; - -import {loadTeamCommands, editCommand} from 'actions/integration_actions.jsx'; -import BackstageHeader from 'components/backstage/components/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'; -import ConfirmModal from 'components/confirm_modal.jsx'; - -const REQUEST_POST = 'P'; -const REQUEST_GET = 'G'; - -export default class EditCommand extends React.Component { - static get propTypes() { - return { - team: PropTypes.object, - location: PropTypes.object - }; - } - - constructor(props) { - super(props); - - this.handleIntegrationChange = this.handleIntegrationChange.bind(this); - - this.submitCommand = this.submitCommand.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleUpdate = this.handleUpdate.bind(this); - this.handleConfirmModal = this.handleConfirmModal.bind(this); - this.confirmModalDismissed = this.confirmModalDismissed.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.originalCommand = null; - this.newCommand = null; - - const teamId = TeamStore.getCurrentId(); - - this.state = { - displayName: '', - description: '', - trigger: '', - url: '', - method: REQUEST_POST, - username: '', - iconUrl: '', - autocomplete: false, - autocompleteHint: '', - autocompleteDescription: '', - saving: false, - serverError: '', - clientError: null, - showConfirmModal: false, - commands: IntegrationStore.getCommands(teamId), - loading: !IntegrationStore.hasReceivedCommands(teamId) - }; - } - - componentDidMount() { - IntegrationStore.addChangeListener(this.handleIntegrationChange); - - if (window.mm_config.EnableCommands === 'true') { - loadTeamCommands(); - } - } - - componentWillUnmount() { - IntegrationStore.removeChangeListener(this.handleIntegrationChange); - } - - handleConfirmModal() { - this.setState({showConfirmModal: true}); - } - - confirmModalDismissed() { - this.setState({showConfirmModal: false}); - } - - submitCommand() { - editCommand( - this.newCmd, - browserHistory.push('/' + this.props.team.name + '/integrations/commands'), - (err) => { - this.setState({ - saving: false, - serverError: err.message - }); - } - ); - } - - handleUpdate() { - this.setState({ - saving: true, - serverError: '', - clientError: '' - }); - - this.submitCommand(); - } - - handleIntegrationChange() { - const teamId = TeamStore.getCurrentId(); - - this.setState({ - commands: IntegrationStore.getCommands(teamId), - loading: !IntegrationStore.hasReceivedCommands(teamId) - }); - - if (!this.state.loading) { - this.originalCommand = this.state.commands.filter((command) => command.id === this.props.location.query.id)[0]; - - this.setState({ - displayName: this.originalCommand.display_name, - description: this.originalCommand.description, - trigger: this.originalCommand.trigger, - url: this.originalCommand.url, - method: this.originalCommand.method, - username: this.originalCommand.username, - iconUrl: this.originalCommand.icon_url, - autocomplete: this.originalCommand.auto_complete, - autocompleteHint: this.originalCommand.auto_complete_hint, - autocompleteDescription: this.originalCommand.auto_complete_desc - }); - } - } - - handleSubmit(e) { - e.preventDefault(); - - if (this.state.saving) { - return; - } - - this.setState({ - saving: true, - serverError: '', - clientError: '' - }); - - let triggerWord = this.state.trigger.trim().toLowerCase(); - if (triggerWord.indexOf('/') === 0) { - triggerWord = triggerWord.substr(1); - } - - const command = { - display_name: this.state.displayName, - description: this.state.description, - trigger: triggerWord, - url: this.state.url.trim(), - method: this.state.method, - username: this.state.username, - icon_url: this.state.iconUrl, - auto_complete: this.state.autocomplete, - team_id: this.props.team.id - }; - - if (this.originalCommand.id) { - command.id = this.originalCommand.id; - } - - 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; - } - - this.newCmd = command; - - if (this.originalCommand.url !== this.newCmd.url || this.originalCommand.trigger !== this.newCmd.trigger || this.originalCommand.method !== this.newCmd.method) { - this.handleConfirmModal(); - this.setState({ - saving: false - }); - } else { - this.submitCommand(); - } - } - - 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() { - const confirmButton = ( - - ); - - const confirmTitle = ( - - ); - - const confirmMessage = ( - - ); - - let autocompleteFields = null; - if (this.state.autocomplete) { - autocompleteFields = [( -
- -
- -
- -
-
-
- ), - ( -
- -
- -
- -
-
-
- )]; - } - - return ( -
- - - - - - -
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
- -
-
- - - - ) - }} - /> -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
- {autocompleteFields} -
- - - - - - - - -
-
-
-
- ); - } -} diff --git a/webapp/components/integrations/components/edit_command/edit_command.jsx b/webapp/components/integrations/components/edit_command/edit_command.jsx new file mode 100644 index 000000000..588047fb3 --- /dev/null +++ b/webapp/components/integrations/components/edit_command/edit_command.jsx @@ -0,0 +1,727 @@ +// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; + +import {FormattedMessage} from 'react-intl'; +import {browserHistory, Link} from 'react-router/es6'; +import Constants from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import FormError from 'components/form_error.jsx'; +import SpinnerButton from 'components/spinner_button.jsx'; +import ConfirmModal from 'components/confirm_modal.jsx'; +import BackstageHeader from 'components/backstage/components/backstage_header.jsx'; + +const REQUEST_POST = 'P'; +const REQUEST_GET = 'G'; + +export default class EditCommand extends React.PureComponent { + static propTypes = { + + /** + * The current team + */ + team: PropTypes.object.isRequired, + + /** + * The id of the command to edit + */ + commandId: PropTypes.string.isRequired, + + /** + * Installed slash commands to display + */ + commands: PropTypes.object, + + /** + * The request state for editCommand action. Contains status and error + */ + editCommandRequest: PropTypes.object.isRequired, + + actions: PropTypes.shape({ + + /** + * The function to call to fetch team commands + */ + getCustomTeamCommands: PropTypes.func.isRequired, + + /** + * The function to call to edit command + */ + editCommand: PropTypes.func.isRequired + }).isRequired + } + + constructor(props) { + super(props); + + this.originalCommand = null; + this.newCommand = null; + + this.state = { + displayName: '', + description: '', + trigger: '', + url: '', + method: REQUEST_POST, + username: '', + iconUrl: '', + autocomplete: false, + autocompleteHint: '', + autocompleteDescription: '', + saving: false, + serverError: '', + clientError: null, + showConfirmModal: false, + loading: true + }; + } + + componentDidMount() { + if (window.mm_config.EnableCommands === 'true') { + this.props.actions.getCustomTeamCommands(this.props.team.id).then( + () => { + this.originalCommand = Object.values(this.props.commands).filter((command) => command.id === this.props.commandId)[0]; + this.setState({ + displayName: this.originalCommand.display_name, + description: this.originalCommand.description, + trigger: this.originalCommand.trigger, + url: this.originalCommand.url, + method: this.originalCommand.method, + username: this.originalCommand.username, + iconUrl: this.originalCommand.icon_url, + autocomplete: this.originalCommand.auto_complete, + autocompleteHint: this.originalCommand.auto_complete_hint, + autocompleteDescription: this.originalCommand.auto_complete_desc, + loading: false + }); + } + ); + } + } + + handleConfirmModal = () => { + this.setState({showConfirmModal: true}); + } + + confirmModalDismissed = () => { + this.setState({showConfirmModal: false}); + } + + submitCommand = async () => { + const data = await this.props.actions.editCommand(this.newCommand); + + if (data) { + browserHistory.push(`/${this.props.team.name}/integrations/commands`); + return; + } + + if (this.props.editCommandRequest.error) { + this.setState({ + saving: false, + serverError: this.props.editCommandRequest.error.message + }); + } + } + + handleUpdate = async () => { + this.setState({ + saving: true, + serverError: '', + clientError: '' + }); + + await this.submitCommand(); + } + + handleSubmit = async (e) => { + e.preventDefault(); + + if (this.state.saving) { + return; + } + + this.setState({ + saving: true, + serverError: '', + clientError: '' + }); + + let triggerWord = this.state.trigger.trim().toLowerCase(); + if (triggerWord.indexOf('/') === 0) { + triggerWord = triggerWord.substr(1); + } + + const command = { + display_name: this.state.displayName, + description: this.state.description, + trigger: triggerWord, + url: this.state.url.trim(), + method: this.state.method, + username: this.state.username, + icon_url: this.state.iconUrl, + auto_complete: this.state.autocomplete, + team_id: this.props.team.id + }; + + if (this.originalCommand.id) { + command.id = this.originalCommand.id; + } + + 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; + } + + this.newCommand = command; + + if (this.originalCommand.url !== this.newCommand.url || + this.originalCommand.trigger !== this.newCommand.trigger || + this.originalCommand.method !== this.newCommand.method) { + this.handleConfirmModal(); + this.setState({ + saving: false + }); + } else { + await this.submitCommand(); + } + } + + 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() { + const confirmButton = ( + + ); + + const confirmTitle = ( + + ); + + const confirmMessage = ( + + ); + + let autocompleteFields = null; + if (this.state.autocomplete) { + autocompleteFields = [( +
+ +
+ +
+ +
+
+
+ ), + ( +
+ +
+ +
+ +
+
+
+ )]; + } + + return ( +
+ + + + + + +
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ +
+
+ + + + ) + }} + /> +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+ {autocompleteFields} +
+ + + + + + + + +
+
+
+
+ ); + } +} diff --git a/webapp/components/integrations/components/edit_command/index.js b/webapp/components/integrations/components/edit_command/index.js new file mode 100644 index 000000000..2a8257113 --- /dev/null +++ b/webapp/components/integrations/components/edit_command/index.js @@ -0,0 +1,31 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {getCustomTeamCommands, editCommand} from 'mattermost-redux/actions/integrations'; +import {getCommands} from 'mattermost-redux/selectors/entities/integrations'; + +import EditCommand from './edit_command.jsx'; + +function mapStateToProps(state, ownProps) { + const commandId = ownProps.location.query.id; + + return { + ...ownProps, + commandId, + commands: getCommands(state), + editCommandRequest: state.requests.integrations.editCommand + }; +} + +function mapDispatchToProps(dispatch) { + return { + actions: bindActionCreators({ + getCustomTeamCommands, + editCommand + }, dispatch) + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(EditCommand); -- cgit v1.2.3-1-g7c22